openclaw - 💡(How to fix) Fix [Bug]: `openclaw <unknown-command> --help` silently shows generic top-level help (exit 0); same command without `--help` correctly errors with `Unknown command` (exit 1)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

$ 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

Root Cause

  • 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.

Fix Action

Fix / Workaround

Not applicable — pure CLI dispatch defect; no model dispatch.

  1. 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).

Code Example

pnpm openclaw heartbeat; echo "exit=$?"
   pnpm openclaw heartbeat --help; echo "exit=$?"

---

$ 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

---

if (
    invocation.hasHelpOrVersion ||   // ← bails out when --help or --version is present
    !primary ||
    primary === "help" ||
    isReservedNonPluginCommandRoot(primary) ||
    isKnownBuiltInCommandRoot(primary)
  ) {
    return null;
  }

---

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)

---

$ 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

---

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);
});
RAW_BUFFERClick to expand / collapse

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

  1. 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.

  2. Run any non-existent subcommand:

    pnpm openclaw heartbeat; echo "exit=$?"
    pnpm openclaw heartbeat --help; echo "exit=$?"
  3. 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:

  1. 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.
  2. 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 <name>" 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:

  1. 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).

  2. 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);
});

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING