Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
openclaw <anything-that-is-not-a-real-subcommand> correctly exits 1 with a clear Unknown command: openclaw <name>. No built-in command or plugin CLI metadata owns "<name>" error. But the same invocation with --help appended silently falls through to the generic top-level help and exits 0, never surfacing that the user's chosen command name doesn't exist. Reproduces on v2026.5.10-beta.1 (today's npm beta) and the same code path exists on v2026.5.7 (today's stable).
The asymmetry is in src/cli/run-main.ts:369-377 — the unknown-primary detection short-circuits whenever invocation.hasHelpOrVersion is true, deferring to the standard help handler instead of erroring like the no---help case does.
Steps to reproduce
-
On v2026.5.10-beta.1 (9c7e67b0f8) or later. Same code path on v2026.5.7 per git show v2026.5.7:src/cli/run-main.ts.
-
Run any non-existent subcommand:
pnpm openclaw heartbeat; echo "exit=$?"
pnpm openclaw heartbeat --help; echo "exit=$?"
-
Observe the asymmetry:
- First form: prints
Unknown command: openclaw heartbeat. …, exits 1. Correct.
- Second form: prints generic top-level help (
Usage: openclaw [options] [command] etc.), exits 0. Incorrect.
Same result for any unknown name (zzz-not-a-real-command, definitelynotacommand, etc.).
Expected behavior
Either:
- Reject consistently.
openclaw <unknown> --help should print the same Unknown command: openclaw <unknown>. No built-in command or plugin CLI metadata owns "<unknown>". error and exit 1, regardless of whether --help is present.
- Reject + steer. Print the
Unknown command error AND then optionally show the top-level help below it as guidance. Still exit 1 so scripts can detect the failure.
Either is consistent with the no---help behavior. The current state silently misleads users who typed a guess to discover whether a command exists.
Actual behavior
Verbatim capture on v2026.5.10-beta.1 (152ea9af34):
$ pnpm openclaw heartbeat
[openclaw] Could not start the CLI.
[openclaw] Reason: Unknown command: openclaw heartbeat. No built-in command or plugin CLI metadata owns "heartbeat".
[openclaw] Debug: set OPENCLAW_DEBUG=1 to include the stack trace.
[openclaw] Try: openclaw doctor
[openclaw] Help: openclaw --help
[ELIFECYCLE] Command failed with exit code 1.
exit=1 ← correct
$ pnpm openclaw heartbeat --help
🦞 OpenClaw 2026.5.10-beta.1 (152ea9a) — All your chats, one OpenClaw.
Usage: openclaw [options] [command]
Options:
--container <name> …
-h, --help Display help for command
…
exit=0 ← buggy: no "Unknown command" error, looks like heartbeat is a real thing
Source trace on v2026.5.10-beta.1:
-
src/cli/run-main.ts:369-377 contains the unknown-primary check that emits the "Unknown command" message:
if (
invocation.hasHelpOrVersion || // ← bails out when --help or --version is present
!primary ||
primary === "help" ||
isReservedNonPluginCommandRoot(primary) ||
isKnownBuiltInCommandRoot(primary)
) {
return null;
}
Returning null here lets the standard help/version handler take over, which prints generic top-level help and exits 0.
-
src/cli/run-main.ts:385-400 (resolveUnownedCliPrimaryMessage) is the function that produces the "Unknown command: openclaw " message that the no---help case correctly emits. It's only reached when the gate at line 369 doesn't bail out.
So the defect is intentional in the sense that --help was given priority — but the resulting UX is wrong: users typo a command, append --help to learn how it works, and OpenClaw silently shows the global help without acknowledging that the typo'd name doesn't exist.
OpenClaw version
Reproduces on:
v2026.5.10-beta.1 (today's npm beta; commit 9c7e67b0f8). Verified live above.
v2026.5.7 (today's npm stable). Source identical: git show v2026.5.7:src/cli/run-main.ts contains the same invocation.hasHelpOrVersion short-circuit at the same logical position.
Operating system
Ubuntu 24.04 (Linux 6.8.0-110-generic). OS-agnostic — pure CLI argv-parsing defect.
Install method
pnpm openclaw … from source checkout. Reproduces on any install path (npm install -g openclaw, openclaw update, etc.).
Model
Not applicable — pure CLI dispatch defect; no model dispatch.
Provider / routing chain
Not applicable.
Additional provider/model setup details
Not relevant. Any default-shape config.
Logs, screenshots, and evidence
Full evidence log saved at qa-reports/11-unknown-command-help-silent-fallthrough/v2026.5.10-beta.1-evidence.log:
openclaw heartbeat --help exit=0 (buggy)
openclaw heartbeat exit=1 (correct)
openclaw zzz-not-a-real-command --help exit=0 (buggy)
openclaw zzz-not-a-real-command exit=1 (correct)
Verifiable in two commands:
$ openclaw heartbeat --help; echo "exit=$?"
…generic help…
exit=0
$ openclaw heartbeat; echo "exit=$?"
[openclaw] Could not start the CLI.
[openclaw] Reason: Unknown command: openclaw heartbeat. …
exit=1
Impact and severity
- Affected: anyone who types
openclaw <name> --help to discover whether a command exists. Particularly painful when users have heard of a feature (like "heartbeat" — which is a real openclaw config concept but not a CLI subcommand) and try to find the CLI surface for it.
- Severity: Low–Medium. UX / discoverability defect, not blocking. But mis-direction is the worst kind of help-system bug — users who get back the generic help conclude the command exists and they just missed the right docs page.
- Frequency: 100% on any unknown name +
--help.
- Concrete consequences:
- Users typing
openclaw heartbeat --help (because heartbeat config exists and they reasonably guess a CLI exists too) get top-level help, conclude heartbeat is a hidden / undocumented command, and waste time digging.
- Shell-completion / discovery scripts that probe
openclaw <name> --help to enumerate valid commands get a false positive on every unknown name.
- Tab-completion users who fat-finger a command and tack on
--help to recover get no useful signal that they mis-typed.
Why this isn't already fixed and isn't a duplicate
- Source-gate is explicit. The
invocation.hasHelpOrVersion short-circuit at src/cli/run-main.ts:369 is intentional code; it just produces the wrong UX. Not a missing-check or oversight.
- Distinct from
#73077 (CLOSED: "parent CLI commands exit 1 when invoked without a subcommand (memory, channels, plugins, approvals, devices, cron, mcp)"). That issue was about real parent commands without a subcommand. Mine is about fake commands with --help.
- Distinct from
#62356 (CLOSED: "CLI enters CPU 100% infinite loop on unknown/unregistered subcommands (v2026.4.5)"). That was a CPU loop on unknown commands; my case is silent fall-through.
- Distinct from
#70349 (CLOSED: "TUI entry point UX: openclaw chat silently does nothing, …"). That was about a real but mis-behaving subcommand; mine is about completely-unknown names.
- Dedup search (open + closed):
openclaw unknown command --help exit code, unknown subcommand --help falls through, cli unknown command help silent, openclaw "Unknown command" help, openclaw cli help unknown command. No existing issue matches the asymmetric --help vs no---help behavior on unknown commands.
Suggested fix sketch
Two reasonable shapes:
-
Smallest: remove the --help/--version early-exit from the unknown-primary check. In src/cli/run-main.ts:369-377, drop invocation.hasHelpOrVersion || from the bail-out condition so the unknown-primary detection runs regardless of --help. Then in the dispatching code, when invocation.hasHelpOrVersion is true AND the primary is unknown, emit the Unknown command error and exit 1 (same as the no---help path does today).
-
Medium: print the error AND show top-level help below it. Even better UX — operator sees both "your command doesn't exist" and "here are the real ones." Exit 1 so scripts still detect the failure.
Either fix is a 5-10 line change. Option 2 is the most user-friendly but option 1 is the minimum.
Regression test:
it("openclaw <unknown> --help errors instead of silently falling through to top-level help", async () => {
const { stdout, stderr, exitCode } = await runCli(["heartbeat", "--help"]);
expect(exitCode).toBe(1);
expect(stderr + stdout).toMatch(/Unknown command: openclaw heartbeat/);
});
it("openclaw <unknown> still errors (regression)", async () => {
const { exitCode } = await runCli(["heartbeat"]);
expect(exitCode).toBe(1);
});
Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
openclaw <anything-that-is-not-a-real-subcommand>correctly exits 1 with a clearUnknown command: openclaw <name>. No built-in command or plugin CLI metadata owns "<name>"error. But the same invocation with--helpappended silently falls through to the generic top-level help and exits 0, never surfacing that the user's chosen command name doesn't exist. Reproduces onv2026.5.10-beta.1(today's npm beta) and the same code path exists onv2026.5.7(today's stable).The asymmetry is in
src/cli/run-main.ts:369-377— the unknown-primary detection short-circuits wheneverinvocation.hasHelpOrVersionis true, deferring to the standard help handler instead of erroring like the no---helpcase does.Steps to reproduce
On
v2026.5.10-beta.1(9c7e67b0f8) or later. Same code path onv2026.5.7pergit show v2026.5.7:src/cli/run-main.ts.Run any non-existent subcommand:
Observe the asymmetry:
Unknown command: openclaw heartbeat. …, exits 1. Correct.Usage: openclaw [options] [command]etc.), exits 0. Incorrect.Same result for any unknown name (
zzz-not-a-real-command,definitelynotacommand, etc.).Expected behavior
Either:
openclaw <unknown> --helpshould print the sameUnknown command: openclaw <unknown>. No built-in command or plugin CLI metadata owns "<unknown>".error and exit 1, regardless of whether--helpis present.Unknown commanderror AND then optionally show the top-level help below it as guidance. Still exit 1 so scripts can detect the failure.Either is consistent with the no-
--helpbehavior. The current state silently misleads users who typed a guess to discover whether a command exists.Actual behavior
Verbatim capture on
v2026.5.10-beta.1(152ea9af34):Source trace on
v2026.5.10-beta.1:src/cli/run-main.ts:369-377contains the unknown-primary check that emits the "Unknown command" message:Returning
nullhere lets the standard help/version handler take over, which prints generic top-level help and exits 0.src/cli/run-main.ts:385-400(resolveUnownedCliPrimaryMessage) is the function that produces the "Unknown command: openclaw " message that the no---helpcase correctly emits. It's only reached when the gate at line 369 doesn't bail out.So the defect is intentional in the sense that
--helpwas given priority — but the resulting UX is wrong: users typo a command, append--helpto learn how it works, and OpenClaw silently shows the global help without acknowledging that the typo'd name doesn't exist.OpenClaw version
Reproduces on:
v2026.5.10-beta.1(today's npm beta; commit9c7e67b0f8). Verified live above.v2026.5.7(today's npm stable). Source identical:git show v2026.5.7:src/cli/run-main.tscontains the sameinvocation.hasHelpOrVersionshort-circuit at the same logical position.Operating system
Ubuntu 24.04 (Linux 6.8.0-110-generic). OS-agnostic — pure CLI argv-parsing defect.
Install method
pnpm openclaw …from source checkout. Reproduces on any install path (npm install -g openclaw,openclaw update, etc.).Model
Not applicable — pure CLI dispatch defect; no model dispatch.
Provider / routing chain
Not applicable.
Additional provider/model setup details
Not relevant. Any default-shape config.
Logs, screenshots, and evidence
Full evidence log saved at
qa-reports/11-unknown-command-help-silent-fallthrough/v2026.5.10-beta.1-evidence.log:Verifiable in two commands:
Impact and severity
openclaw <name> --helpto discover whether a command exists. Particularly painful when users have heard of a feature (like "heartbeat" — which is a real openclaw config concept but not a CLI subcommand) and try to find the CLI surface for it.--help.openclaw heartbeat --help(because heartbeat config exists and they reasonably guess a CLI exists too) get top-level help, conclude heartbeat is a hidden / undocumented command, and waste time digging.openclaw <name> --helpto enumerate valid commands get a false positive on every unknown name.--helpto recover get no useful signal that they mis-typed.Why this isn't already fixed and isn't a duplicate
invocation.hasHelpOrVersionshort-circuit atsrc/cli/run-main.ts:369is intentional code; it just produces the wrong UX. Not a missing-check or oversight.#73077(CLOSED: "parent CLI commands exit 1 when invoked without a subcommand (memory, channels, plugins, approvals, devices, cron, mcp)"). That issue was about real parent commands without a subcommand. Mine is about fake commands with--help.#62356(CLOSED: "CLI enters CPU 100% infinite loop on unknown/unregistered subcommands (v2026.4.5)"). That was a CPU loop on unknown commands; my case is silent fall-through.#70349(CLOSED: "TUI entry point UX:openclaw chatsilently does nothing, …"). That was about a real but mis-behaving subcommand; mine is about completely-unknown names.openclaw unknown command --help exit code,unknown subcommand --help falls through,cli unknown command help silent,openclaw "Unknown command" help,openclaw cli help unknown command. No existing issue matches the asymmetric--helpvs no---helpbehavior on unknown commands.Suggested fix sketch
Two reasonable shapes:
Smallest: remove the
--help/--versionearly-exit from the unknown-primary check. Insrc/cli/run-main.ts:369-377, dropinvocation.hasHelpOrVersion ||from the bail-out condition so the unknown-primary detection runs regardless of--help. Then in the dispatching code, wheninvocation.hasHelpOrVersionis true AND the primary is unknown, emit theUnknown commanderror and exit 1 (same as the no---helppath does today).Medium: print the error AND show top-level help below it. Even better UX — operator sees both "your command doesn't exist" and "here are the real ones." Exit 1 so scripts still detect the failure.
Either fix is a 5-10 line change. Option 2 is the most user-friendly but option 1 is the minimum.
Regression test: