fix(cli): improve config recovery copy

This commit is contained in:
Vincent Koc
2026-05-09 07:56:25 +08:00
parent 938cc3628c
commit f09bb6d75c
4 changed files with 24 additions and 9 deletions

View File

@@ -529,7 +529,7 @@ describe("config cli", () => {
await expect(runConfigCommand(["config", "validate"])).rejects.toThrow("__exit__:1");
expect(mockError).toHaveBeenCalledWith(expect.stringContaining("Config invalid at"));
expect(mockError).toHaveBeenCalledWith(expect.stringContaining("config is invalid"));
expect(mockError).toHaveBeenCalledWith(
expect.stringContaining("agents.defaults.suppressToolErrorWarnings"),
);

View File

@@ -235,7 +235,7 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
}
function formatDoctorHint(message: string): string {
return `Run \`${formatCliCommand("openclaw doctor")}\` ${message}`;
return `Run \`${formatCliCommand("openclaw doctor --fix")}\` ${message}`;
}
function formatUnsupportedSecretRefPolicyFailureMessage(issues: string[]): string {
@@ -525,7 +525,7 @@ async function loadValidConfig(runtime: RuntimeEnv = defaultRuntime) {
if (snapshot.valid) {
return snapshot;
}
runtime.error(`Config invalid at ${shortenHomePath(snapshot.path)}.`);
runtime.error(`OpenClaw config is invalid: ${shortenHomePath(snapshot.path)}`);
for (const line of formatConfigIssueLines(snapshot.issues, "-", { normalizeRoot: true })) {
runtime.error(line);
}
@@ -1662,7 +1662,11 @@ export async function runConfigGet(opts: { path: string; json?: boolean; runtime
const redacted = redactConfigObject(snapshot.config);
const res = getAtPath(redacted, parsedPath);
if (!res.found) {
runtime.error(danger(`Config path not found: ${opts.path}`));
runtime.error(
danger(
`Config path not found: ${opts.path}. Run ${formatCliCommand("openclaw config validate")} to inspect config shape.`,
),
);
runtime.exit(1);
return;
}
@@ -1696,7 +1700,11 @@ export async function runConfigUnset(opts: { path: string; runtime?: RuntimeEnv
const next = structuredClone(snapshot.resolved) as Record<string, unknown>;
const unsetResult = unsetAtPath(next, parsedPath);
if (!unsetResult.removed) {
runtime.error(danger(`Config path not found: ${opts.path}`));
runtime.error(
danger(
`Config path not found: ${opts.path}. Nothing was changed. Run ${formatCliCommand("openclaw config get <path>")} first if you are unsure of the path.`,
),
);
runtime.exit(1);
return;
}
@@ -1763,6 +1771,9 @@ export async function runConfigValidate(opts: { json?: boolean; runtime?: Runtim
writeRuntimeJson(runtime, { valid: false, path: outputPath, error: "file not found" }, 0);
} else {
runtime.error(danger(`Config file not found: ${shortPath}`));
runtime.error(
`Create one with ${formatCliCommand("openclaw onboard")} or run ${formatCliCommand("openclaw doctor --fix")}.`,
);
}
runtime.exit(1);
return;
@@ -1774,12 +1785,13 @@ export async function runConfigValidate(opts: { json?: boolean; runtime?: Runtim
if (opts.json) {
writeRuntimeJson(runtime, { valid: false, path: outputPath, issues });
} else {
runtime.error(danger(`Config invalid at ${shortPath}:`));
runtime.error(danger(`OpenClaw config is invalid: ${shortPath}`));
for (const line of formatConfigIssueLines(issues, danger("×"), { normalizeRoot: true })) {
runtime.error(` ${line}`);
}
runtime.error("");
runtime.error(formatDoctorHint("to repair, or fix the keys above manually."));
runtime.error(`Inspect with ${formatCliCommand("openclaw config validate")}.`);
}
runtime.exit(1);
return;

View File

@@ -21,8 +21,9 @@ export async function requireValidConfigFileSnapshot(
snapshot.issues.length > 0
? formatConfigIssueLines(snapshot.issues, "-").join("\n")
: "Unknown validation issue.";
runtime.error(`Config invalid:\n${issues}`);
runtime.error(`Fix the config or run ${formatCliCommand("openclaw doctor")}.`);
runtime.error(`OpenClaw config is invalid: ${snapshot.path}\n${issues}`);
runtime.error(`Fix: ${formatCliCommand("openclaw doctor --fix")}`);
runtime.error(`Inspect: ${formatCliCommand("openclaw config validate")}`);
runtime.exit(1);
return null;
}

View File

@@ -32,7 +32,9 @@ function resolveNodeRunner(): NodeRunner {
if (hasBinary("npx")) {
return { cmd: "npx", args: ["-y"] };
}
throw new Error("Missing pnpm or npx; install a Node package runner.");
throw new Error(
`Docs search needs pnpm or npx to run the docs search helper. Install pnpm, or run ${formatCliCommand("npm install -g pnpm")}.`,
);
}
async function runNodeTool(tool: string, toolArgs: string[], options: ToolRunOptions = {}) {