mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 10:55:31 -06:00
fix(cli): render errors through redacting formatter (#123929)
This commit is contained in:
committed by
GitHub
parent
f570022e14
commit
2463ea0a5e
@@ -475,7 +475,7 @@ describe("serveAcpGateway startup", () => {
|
||||
opts: { verbose: true },
|
||||
expected: [
|
||||
"openclaw acp: gateway event chat failed\n",
|
||||
"openclaw acp: gateway event chat error: Error: handler boom\n",
|
||||
"openclaw acp: gateway event chat error: handler boom\n",
|
||||
],
|
||||
},
|
||||
])("contains rejected gateway event handling with $name", async ({ opts, expected }) => {
|
||||
|
||||
+7
-4
@@ -21,6 +21,7 @@ import { getRuntimeConfig } from "../config/config.js";
|
||||
import { resolveGatewayClientBootstrap } from "../gateway/client-bootstrap.js";
|
||||
import { startGatewayClientWhenEventLoopReady } from "../gateway/client-start-readiness.js";
|
||||
import { GatewayClient } from "../gateway/client.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { isMainModule } from "../infra/is-main.js";
|
||||
import { routeLogsToStderr } from "../logging/console.js";
|
||||
import { closeOpenClawStateDatabase } from "../state/openclaw-state-db.js";
|
||||
@@ -137,7 +138,7 @@ export async function serveAcpGateway(opts: AcpServerOptions = {}): Promise<void
|
||||
try {
|
||||
closeOpenClawStateDatabase();
|
||||
} catch (err) {
|
||||
console.warn(`acp: state database close failed during shutdown: ${String(err)}`);
|
||||
console.warn(`acp: state database close failed during shutdown: ${formatErrorMessage(err)}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -161,7 +162,9 @@ export async function serveAcpGateway(opts: AcpServerOptions = {}): Promise<void
|
||||
void agent?.handleGatewayEvent(evt).catch((err: unknown) => {
|
||||
process.stderr.write(`openclaw acp: gateway event ${evt.event} failed\n`);
|
||||
if (opts.verbose) {
|
||||
process.stderr.write(`openclaw acp: gateway event ${evt.event} error: ${String(err)}\n`);
|
||||
process.stderr.write(
|
||||
`openclaw acp: gateway event ${evt.event} error: ${formatErrorMessage(err)}\n`,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -202,7 +205,7 @@ export async function serveAcpGateway(opts: AcpServerOptions = {}): Promise<void
|
||||
agent = null;
|
||||
activeAgent?.shutdown();
|
||||
const gatewayStop = gateway.stopAndWait().catch((err: unknown) => {
|
||||
console.warn(`acp: gateway stop failed during shutdown: ${String(err)}`);
|
||||
console.warn(`acp: gateway stop failed during shutdown: ${formatErrorMessage(err)}`);
|
||||
});
|
||||
await gatewayStop;
|
||||
closeStateDatabase();
|
||||
@@ -420,7 +423,7 @@ if (isMainModule({ currentFile: fileURLToPath(import.meta.url) })) {
|
||||
}
|
||||
const opts = parseArgs(argv);
|
||||
serveAcpGateway(opts).catch((err: unknown) => {
|
||||
console.error(String(err));
|
||||
console.error(formatErrorMessage(err));
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ describe("runGatewayHealthJsonRoute", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves the existing error contract when local config resolution fails", async () => {
|
||||
it("formats local config resolution failures", async () => {
|
||||
const runtime = createRuntime();
|
||||
const error = new Error("config unavailable");
|
||||
const callGateway = vi.fn();
|
||||
@@ -101,7 +101,7 @@ describe("runGatewayHealthJsonRoute", () => {
|
||||
|
||||
expect(callGateway).not.toHaveBeenCalled();
|
||||
expect(runtime.writeJson).not.toHaveBeenCalled();
|
||||
expect(runtime.error).toHaveBeenCalledWith(String(error));
|
||||
expect(runtime.error).toHaveBeenCalledWith(error.message);
|
||||
expect(runtime.exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Route-first machine-readable Gateway health command.
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
|
||||
type GatewayHealthRpcOpts = Parameters<
|
||||
@@ -62,7 +63,7 @@ export async function runGatewayHealthJsonRoute(
|
||||
);
|
||||
} catch (error) {
|
||||
if (!rpc) {
|
||||
runtime.error(String(error));
|
||||
runtime.error(formatErrorMessage(error));
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -149,9 +149,7 @@ describe("cli program (smoke)", () => {
|
||||
|
||||
it("rejects partial tui history limits", async () => {
|
||||
await expect(runProgram(["tui", "--history-limit", "10x"])).rejects.toThrow("exit");
|
||||
expect(runtime.error).toHaveBeenCalledWith(
|
||||
"Error: --history-limit must be a positive integer.",
|
||||
);
|
||||
expect(runtime.error).toHaveBeenCalledWith("--history-limit must be a positive integer.");
|
||||
expect(tuiRunMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -175,7 +173,7 @@ describe("cli program (smoke)", () => {
|
||||
it("rejects tui history limits above the Gateway maximum", async () => {
|
||||
await expect(runProgram(["tui", "--history-limit", "1001"])).rejects.toThrow("exit");
|
||||
|
||||
expect(runtime.error).toHaveBeenCalledWith("Error: --history-limit must be at most 1000.");
|
||||
expect(runtime.error).toHaveBeenCalledWith("--history-limit must be at most 1000.");
|
||||
expect(tuiRunMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -274,9 +274,7 @@ describe("registerQrCli", () => {
|
||||
});
|
||||
|
||||
await expect(runQr(["--setup-code-only", "--limited", "--voice-node"])).rejects.toThrow("exit");
|
||||
expect(runtime.error).toHaveBeenCalledWith(
|
||||
"Error: Use either --limited or --voice-node, not both.",
|
||||
);
|
||||
expect(runtime.error).toHaveBeenCalledWith("Use either --limited or --voice-node, not both.");
|
||||
});
|
||||
|
||||
it("renders ASCII QR by default", async () => {
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { hasConfiguredSecretInput } from "../config/types.secrets.js";
|
||||
import { trimToUndefined } from "../gateway/credentials.js";
|
||||
import { resolveRequiredConfiguredSecretRefInputString } from "../gateway/resolve-configured-secret-input-string.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { loadGatewayTlsRuntime } from "../infra/tls/gateway.js";
|
||||
import { renderQrTerminal } from "../media/qr-terminal.ts";
|
||||
import { resolvePairingSetupFromConfig, encodePairingSetupCode } from "../pairing/setup-code.js";
|
||||
@@ -282,7 +283,7 @@ export function registerQrCli(program: Command) {
|
||||
|
||||
defaultRuntime.log(lines.join("\n"));
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { Command } from "commander";
|
||||
import { formatDocsLink } from "../../packages/terminal-core/src/links.js";
|
||||
import { theme } from "../../packages/terminal-core/src/theme.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { addTuiOptions } from "./tui-cli-options.js";
|
||||
|
||||
@@ -31,7 +32,7 @@ export function registerResumeCli(program: Command) {
|
||||
const { runResumeCommand } = await import("./resume-cli.runtime.js");
|
||||
await runResumeCommand(query, opts);
|
||||
} catch (error) {
|
||||
defaultRuntime.error(String(error));
|
||||
defaultRuntime.error(formatErrorMessage(error));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { formatDocsLink } from "../../packages/terminal-core/src/links.js";
|
||||
import { theme } from "../../packages/terminal-core/src/theme.js";
|
||||
import { sandboxExplainCommand } from "../commands/sandbox-explain.js";
|
||||
import { sandboxListCommand, sandboxRecreateCommand } from "../commands/sandbox.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { formatHelpExamples } from "./help-format.js";
|
||||
|
||||
@@ -50,7 +51,7 @@ function createRunner(
|
||||
try {
|
||||
await commandFn(opts, defaultRuntime);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1477,6 +1477,33 @@ describe("skills cli commands", () => {
|
||||
expectStatusWorkspaceCall("/tmp/workspace-main");
|
||||
});
|
||||
|
||||
it("renders named agent-selection errors without the internal class name", async () => {
|
||||
const error = new Error(
|
||||
"Multiple agents are configured, but this operation has no explicit owner.",
|
||||
);
|
||||
error.name = "AgentSelectionRequiredError";
|
||||
resolveDefaultAgentIdMock.mockImplementationOnce(() => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
await expect(runCommand(["skills", "list"])).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(runtimeErrors).toStrictEqual([error.message]);
|
||||
});
|
||||
|
||||
it("redacts secrets from rendered skills CLI errors", async () => {
|
||||
const secret = "sk-abcdefghijklmnopqrstuv";
|
||||
resolveDefaultAgentIdMock.mockImplementationOnce(() => {
|
||||
throw new Error(`Skill lookup failed with token=${secret}`);
|
||||
});
|
||||
|
||||
await expect(runCommand(["skills", "list"])).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(runtimeErrors).toHaveLength(1);
|
||||
expect(runtimeErrors[0]).toContain("Skill lookup failed");
|
||||
expect(runtimeErrors[0]).not.toContain(secret);
|
||||
});
|
||||
|
||||
it("keeps non-JSON skills list output on stdout with human-readable formatting", async () => {
|
||||
await runCommand(["skills", "list"]);
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ describe("skills curator cli", () => {
|
||||
from: "user",
|
||||
}),
|
||||
).rejects.toThrow("__exit__:1");
|
||||
expect(mocks.defaultRuntime.error).toHaveBeenCalledWith("Error: remote unavailable");
|
||||
expect(mocks.defaultRuntime.error).toHaveBeenCalledWith("remote unavailable");
|
||||
});
|
||||
|
||||
it("disambiguates duplicate skill keys in text status", async () => {
|
||||
|
||||
+10
-9
@@ -23,6 +23,7 @@ import {
|
||||
fetchClawHubSkillVerification,
|
||||
type ClawHubSkillVerificationResponse,
|
||||
} from "../infra/clawhub-skills.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import {
|
||||
installSkillFromClawHub,
|
||||
@@ -209,7 +210,7 @@ async function runSkillsAction(
|
||||
const report = await loadSkillsStatusReport(options);
|
||||
defaultRuntime.writeStdout(render(report));
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -601,7 +602,7 @@ export function registerSkillsCli(program: Command) {
|
||||
defaultRuntime.log(`${skillRef}${version} ${displayName}${summary}${trust}`);
|
||||
}
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
@@ -734,7 +735,7 @@ export function registerSkillsCli(program: Command) {
|
||||
}
|
||||
defaultRuntime.log(`Installed ${result.slug}@${result.version} -> ${result.targetDir}`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
},
|
||||
@@ -834,7 +835,7 @@ export function registerSkillsCli(program: Command) {
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
},
|
||||
@@ -917,7 +918,7 @@ export function registerSkillsCli(program: Command) {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
reportError(String(err));
|
||||
reportError(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
@@ -941,7 +942,7 @@ export function registerSkillsCli(program: Command) {
|
||||
}
|
||||
defaultRuntime.writeStdout(formatSkillCuratorStatus(status));
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
};
|
||||
@@ -967,7 +968,7 @@ export function registerSkillsCli(program: Command) {
|
||||
`${action[0]?.toUpperCase()}${action.slice(1)} ${result.skillKey}\n`,
|
||||
);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
@@ -996,7 +997,7 @@ export function registerSkillsCli(program: Command) {
|
||||
}
|
||||
defaultRuntime.writeStdout(format(result));
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
};
|
||||
@@ -1061,7 +1062,7 @@ export function registerSkillsCli(program: Command) {
|
||||
}
|
||||
defaultRuntime.writeStdout(formatSkillProposalInspect(proposal));
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -284,7 +284,7 @@ describe("skills verify CLI", () => {
|
||||
).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(JSON.parse(mocks.runtimeStdout.at(-1) ?? "{}")).toEqual({
|
||||
error: "Error: ClawHub verification unavailable",
|
||||
error: "ClawHub verification unavailable",
|
||||
});
|
||||
expect(mocks.runtimeErrors).toStrictEqual([]);
|
||||
});
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ import type { Command } from "commander";
|
||||
import { CHAT_HISTORY_MAX_ENTRIES } from "../../packages/gateway-protocol/src/schema/chat-history-constants.js";
|
||||
import { formatDocsLink } from "../../packages/terminal-core/src/links.js";
|
||||
import { theme } from "../../packages/terminal-core/src/theme.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { parseTimeoutMs } from "./parse-timeout.js";
|
||||
import { resolveSessionTarget } from "./session-target.js";
|
||||
@@ -123,7 +124,7 @@ export function registerTuiCli(program: Command) {
|
||||
const invokedSubcommand = cmd.parent?.args[0];
|
||||
await runTuiCliAction(target, opts, invokedSubcommand);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { Command } from "commander";
|
||||
import { formatDocsLink } from "../../packages/terminal-core/src/links.js";
|
||||
import { theme } from "../../packages/terminal-core/src/theme.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { inheritOptionFromParent } from "./command-options.js";
|
||||
import { formatHelpExamples } from "./help-format.js";
|
||||
@@ -118,7 +119,7 @@ function registerUpdateFinalizationCommand(update: Command, name: string, hidden
|
||||
normalizeCommanderClawHubRiskOption(opts) || inheritedUpdateClawHubRisk(actionCommand),
|
||||
});
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
@@ -209,7 +210,7 @@ ${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.openclaw.ai/cli/up
|
||||
acknowledgeClawHubRisk: normalizeCommanderClawHubRiskOption(opts),
|
||||
});
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
@@ -235,7 +236,7 @@ ${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.openclaw.ai/cli/up
|
||||
timeout: inheritedUpdateTimeout(opts, command),
|
||||
});
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
@@ -265,7 +266,7 @@ ${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.openclaw.ai/cli/up
|
||||
timeout: inheritedUpdateTimeout(opts, command),
|
||||
});
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { theme } from "../../../packages/terminal-core/src/theme.js";
|
||||
import { readConfigFileSnapshot } from "../../config/config.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { readGatewayServiceState, resolveGatewayService } from "../../daemon/service.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import type { UpdateChannel } from "../../infra/update-channels.js";
|
||||
import { compareSemverStrings } from "../../infra/update-check.js";
|
||||
import {
|
||||
@@ -437,7 +438,7 @@ export async function finishUpdate(params: {
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof GatewayServiceUpdateOwnershipError) {
|
||||
defaultRuntime.error(err.message);
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { selectStyled } from "../../../packages/terminal-core/src/prompt-select-
|
||||
import { stylePromptMessage } from "../../../packages/terminal-core/src/prompt-style.js";
|
||||
import { theme } from "../../../packages/terminal-core/src/theme.js";
|
||||
import { readConfigFileSnapshot } from "../../config/config.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import {
|
||||
formatUpdateChannelLabel,
|
||||
normalizeUpdateChannel,
|
||||
@@ -155,7 +156,7 @@ export async function updateWizardCommand(opts: UpdateWizardOptions = {}): Promi
|
||||
timeout: opts.timeout,
|
||||
});
|
||||
} catch (err) {
|
||||
defaultRuntime.error(String(err));
|
||||
defaultRuntime.error(formatErrorMessage(err));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { migratePersistedImplicitMainRoster } from "../config/legacy.roster.js";
|
||||
import { logConfigUpdated } from "../config/logging.js";
|
||||
import type { AgentConfig, IdentityConfig } from "../config/types.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
@@ -136,7 +137,7 @@ export async function agentsSetIdentityCommand(
|
||||
try {
|
||||
identityFromFile = await loadAgentIdentityFromFile(identityFilePath);
|
||||
} catch (error) {
|
||||
runtime.error(String(error instanceof Error ? error.message : error));
|
||||
runtime.error(formatErrorMessage(error));
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -352,9 +352,12 @@ describe("agents set-identity command", () => {
|
||||
|
||||
await agentsSetIdentityCommand({ agent: "main", identityFile: identityPath }, runtime);
|
||||
|
||||
expect(runtime.error).toHaveBeenCalledWith(
|
||||
const renderedError = String(runtime.error.mock.calls[0]?.[0]);
|
||||
expect(renderedError).toContain(
|
||||
`Identity file ${identityPath} exceeds the maximum size of ${TEST_MAX_IDENTITY_FILE_BYTES} bytes`,
|
||||
);
|
||||
expect(renderedError).toContain(`File exceeds ${TEST_MAX_IDENTITY_FILE_BYTES} bytes:`);
|
||||
expect(renderedError).toContain("too-large");
|
||||
expect(runtime.exit).toHaveBeenCalledWith(1);
|
||||
expect(configMocks.writeConfigFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user