fix(config): require doctor for invalid configs (#764 — thanks @mukhtharcm)
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
## 2026.1.12-4
|
||||
|
||||
### Fixes
|
||||
- Onboarding/Configure: refuse to proceed with invalid configs; run `clawdbot doctor` first to avoid wiping custom fields. (#764 — thanks @mukhtharcm)
|
||||
- Anthropic: merge consecutive user turns (preserve newest metadata) before validation to avoid “Incorrect role information” errors. (#804 — thanks @ThomsenDrake)
|
||||
- Discord/Slack: centralize reply-thread planning so auto-thread replies stay in the created thread without parent reply refs.
|
||||
- Update: run `clawdbot doctor --non-interactive` during updates to avoid TTY hangs. (#781 — thanks @ronyrus)
|
||||
|
||||
@@ -64,6 +64,8 @@ Tip: `--json` does **not** imply non-interactive mode. Use `--non-interactive` (
|
||||
|
||||
1) **Existing config detection**
|
||||
- If `~/.clawdbot/clawdbot.json` exists, choose **Keep / Modify / Reset**.
|
||||
- If the config is invalid or contains legacy keys, the wizard stops and asks
|
||||
you to run `clawdbot doctor` before continuing.
|
||||
- Reset uses `trash` (never `rm`) and offers scopes:
|
||||
- Config only
|
||||
- Config + credentials + sessions
|
||||
|
||||
@@ -586,7 +586,7 @@ export async function runConfigureWizard(
|
||||
const prompter = createClackPrompter();
|
||||
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
let baseConfig: ClawdbotConfig = snapshot.valid ? snapshot.config : {};
|
||||
const baseConfig: ClawdbotConfig = snapshot.valid ? snapshot.config : {};
|
||||
|
||||
if (snapshot.exists) {
|
||||
const title = snapshot.valid
|
||||
@@ -604,14 +604,11 @@ export async function runConfigureWizard(
|
||||
);
|
||||
}
|
||||
if (!snapshot.valid) {
|
||||
const reset = guardCancel(
|
||||
await confirm({
|
||||
message: "Config invalid. Start fresh?",
|
||||
initialValue: true,
|
||||
}),
|
||||
runtime,
|
||||
outro(
|
||||
"Config invalid. Run `clawdbot doctor` to repair it, then re-run configure.",
|
||||
);
|
||||
if (reset) baseConfig = {};
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,13 @@ export async function runNonInteractiveOnboarding(
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
) {
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
if (snapshot.exists && !snapshot.valid) {
|
||||
runtime.error(
|
||||
"Config invalid. Run `clawdbot doctor` to repair it, then re-run onboarding.",
|
||||
);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
const baseConfig: ClawdbotConfig = snapshot.valid ? snapshot.config : {};
|
||||
const mode = opts.mode ?? "local";
|
||||
if (mode !== "local" && mode !== "remote") {
|
||||
|
||||
@@ -287,6 +287,10 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
||||
}
|
||||
|
||||
const legacyIssues = findLegacyConfigIssues(resolved);
|
||||
const resolvedConfig =
|
||||
typeof resolved === "object" && resolved !== null
|
||||
? (resolved as ClawdbotConfig)
|
||||
: {};
|
||||
|
||||
const validated = validateConfigObject(resolved);
|
||||
if (!validated.ok) {
|
||||
@@ -296,7 +300,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
||||
raw,
|
||||
parsed: parsedRes.parsed,
|
||||
valid: false,
|
||||
config: resolved as ClawdbotConfig,
|
||||
config: resolvedConfig,
|
||||
issues: validated.issues,
|
||||
legacyIssues,
|
||||
};
|
||||
|
||||
@@ -80,6 +80,58 @@ vi.mock("../tui/tui.js", () => ({
|
||||
}));
|
||||
|
||||
describe("runOnboardingWizard", () => {
|
||||
it("exits when config is invalid", async () => {
|
||||
readConfigFileSnapshot.mockResolvedValueOnce({
|
||||
path: "/tmp/.clawdbot/clawdbot.json",
|
||||
exists: true,
|
||||
raw: "{}",
|
||||
parsed: {},
|
||||
valid: false,
|
||||
config: {},
|
||||
issues: [{ path: "routing.allowFrom", message: "Legacy key" }],
|
||||
legacyIssues: [{ path: "routing.allowFrom", message: "Legacy key" }],
|
||||
});
|
||||
|
||||
const select: WizardPrompter["select"] = vi.fn(async () => "quickstart");
|
||||
const prompter: WizardPrompter = {
|
||||
intro: vi.fn(async () => {}),
|
||||
outro: vi.fn(async () => {}),
|
||||
note: vi.fn(async () => {}),
|
||||
select,
|
||||
multiselect: vi.fn(async () => []),
|
||||
text: vi.fn(async () => ""),
|
||||
confirm: vi.fn(async () => false),
|
||||
progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
|
||||
};
|
||||
|
||||
const runtime: RuntimeEnv = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn((code: number) => {
|
||||
throw new Error(`exit:${code}`);
|
||||
}),
|
||||
};
|
||||
|
||||
await expect(
|
||||
runOnboardingWizard(
|
||||
{
|
||||
flow: "quickstart",
|
||||
authChoice: "skip",
|
||||
installDaemon: false,
|
||||
skipProviders: true,
|
||||
skipSkills: true,
|
||||
skipHealth: true,
|
||||
skipUi: true,
|
||||
},
|
||||
runtime,
|
||||
prompter,
|
||||
),
|
||||
).rejects.toThrow("exit:1");
|
||||
|
||||
expect(select).not.toHaveBeenCalled();
|
||||
expect(prompter.outro).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("skips prompts and setup steps when flags are set", async () => {
|
||||
const select: WizardPrompter["select"] = vi.fn(async () => "quickstart");
|
||||
const multiselect: WizardPrompter["multiselect"] = vi.fn(async () => []);
|
||||
|
||||
@@ -96,6 +96,14 @@ export async function runOnboardingWizard(
|
||||
);
|
||||
}
|
||||
|
||||
if (!snapshot.valid) {
|
||||
await prompter.outro(
|
||||
"Config invalid. Run `clawdbot doctor` to repair it, then re-run onboarding.",
|
||||
);
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
const action = (await prompter.select({
|
||||
message: "Config handling",
|
||||
options: [
|
||||
@@ -124,8 +132,6 @@ export async function runOnboardingWizard(
|
||||
})) as ResetScope;
|
||||
await handleReset(resetScope, resolveUserPath(workspaceDefault), runtime);
|
||||
baseConfig = {};
|
||||
} else if (action === "keep" && !snapshot.valid) {
|
||||
baseConfig = {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user