Skip to content

Commit cc545c5

Browse files
YB0yYB0y
authored andcommitted
fix(cli): error on unknown command root when --help/--version is appended (#81077)
openclaw <unknown-command> --help silently fell through to generic top-level help and exited 0, while the same openclaw <unknown-command> (no --help) correctly errored with "Unknown command" and exited 1. The asymmetry was caused by two short-circuits on invocation.hasHelpOrVersion: resolveUnownedCliPrimary bailed before the unknown-primary check, and the only caller was gated on !isHelpOrVersionInvocation. Drop the hasHelpOrVersion bail in resolveUnownedCliPrimary so the helper continues to validate the primary when --help or --version is present, and add an unconditional caller in runCli after the root and browser help fast paths so legitimate help (openclaw --help, openclaw status --help, openclaw browser --help) still dispatches normally while unknown roots error consistently regardless of trailing flags. Verified: - pnpm test src/cli/run-main.exit.test.ts src/cli/argv.test.ts src/cli/argv-invocation.test.ts (76 passed) - pnpm exec oxfmt --check --threads=1 src/cli/run-main.ts src/cli/run-main.exit.test.ts (clean) - pnpm build && node openclaw.mjs heartbeat --help (exit 1, Unknown command error) - pnpm build && node openclaw.mjs status --help (exit 0, status help renders) - pnpm build && node openclaw.mjs browser --help (exit 0, browser help renders) - pnpm build && node openclaw.mjs --help (exit 0, root help renders) Closes #81077
1 parent c339980 commit cc545c5

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

src/cli/run-main.exit.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,26 @@ describe("runCli exit behavior", () => {
504504
expect(registerPluginCliCommandsFromValidatedConfigMock).not.toHaveBeenCalled();
505505
});
506506

507+
it("rejects unowned command roots even when --help is appended (regression for #81077)", async () => {
508+
await expect(runCli(["node", "openclaw", "foo", "--help"])).rejects.toThrow(
509+
'No built-in command or plugin CLI metadata owns "foo"',
510+
);
511+
512+
expect(startProxyMock).not.toHaveBeenCalled();
513+
expect(tryRouteCliMock).not.toHaveBeenCalled();
514+
expect(buildProgramMock).not.toHaveBeenCalled();
515+
expect(registerPluginCliCommandsFromValidatedConfigMock).not.toHaveBeenCalled();
516+
});
517+
518+
it("rejects unowned command roots even when --version is appended", async () => {
519+
await expect(runCli(["node", "openclaw", "foo", "--version"])).rejects.toThrow(
520+
'No built-in command or plugin CLI metadata owns "foo"',
521+
);
522+
523+
expect(startProxyMock).not.toHaveBeenCalled();
524+
expect(tryRouteCliMock).not.toHaveBeenCalled();
525+
});
526+
507527
it("does not suggest plugins.allow for unknown command roots before proxy startup", async () => {
508528
loadConfigMock.mockReturnValueOnce({
509529
plugins: {

src/cli/run-main.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,6 @@ async function resolveUnownedCliPrimary(params: {
367367
const invocation = resolveCliArgvInvocation(rewriteUpdateFlagArgv(params.argv));
368368
const { primary } = invocation;
369369
if (
370-
invocation.hasHelpOrVersion ||
371370
!primary ||
372371
primary === "help" ||
373372
isReservedNonPluginCommandRoot(primary) ||
@@ -541,6 +540,19 @@ export async function runCli(argv: string[] = process.argv) {
541540
}
542541
}
543542

543+
// Reject unowned command roots before help/version routing, so that
544+
// `openclaw <typo> --help` surfaces the same Unknown command error as
545+
// `openclaw <typo>` instead of silently showing generic top-level help.
546+
// Runs after the root and browser help fast paths so legitimate help still
547+
// dispatches normally. See #81077.
548+
{
549+
const config = await readBestEffortCliConfig();
550+
const unownedPrimary = await resolveUnownedCliPrimary({ argv: normalizedArgv, config });
551+
if (unownedPrimary) {
552+
throw new Error(await resolveUnownedCliPrimaryMessage({ primary: unownedPrimary, config }));
553+
}
554+
}
555+
544556
const shouldRunBareRootCrestodian = shouldStartCrestodianForBareRoot(normalizedArgv);
545557
const shouldRunModernOnboardCrestodian = shouldStartCrestodianForModernOnboard(normalizedArgv);
546558
if (shouldRunBareRootCrestodian || shouldRunModernOnboardCrestodian) {

0 commit comments

Comments
 (0)