fix(cli): reject unknown command help roots (#81083) (thanks @YB0y)

Behavior addressed: Unknown CLI command roots now error consistently even when --help or --version is appended, while legitimate built-in help fast paths still render normally.

Real environment tested: Local OpenClaw source checkout plus GitHub workflow run-level status.

Exact steps or command run after this patch: pnpm test src/cli/run-main.exit.test.ts src/cli/argv.test.ts src/cli/argv-invocation.test.ts; pnpm exec oxfmt --check --threads=1 src/cli/run-main.ts src/cli/run-main.exit.test.ts; autoreview --mode branch --base origin/main --no-web-search.

Evidence after fix: Focused CLI test shards passed 178 tests; formatter clean; autoreview reported no accepted/actionable findings; GitHub CI run 26422344121 and CodeQL Critical Quality run 26422344090 completed successfully.

Observed result after fix: `openclaw foo --help` and `openclaw foo --version` reject before proxy/program startup, while known help fast paths remain ahead of the unknown-root guard.

What was not tested: Full local build; contributor PR body already supplied build/CLI command proof before rebase.

Co-authored-by: YB0y <brianandez6@gmail.com>
This commit is contained in:
YBoy
2026-05-26 05:38:43 +07:00
committed by GitHub
parent 68ab48b179
commit bec7d56b73
2 changed files with 33 additions and 1 deletions

View File

@@ -672,6 +672,26 @@ describe("runCli exit behavior", () => {
expect(registerPluginCliCommandsFromValidatedConfigMock).not.toHaveBeenCalled();
});
it("rejects unowned command roots even when --help is appended (regression for #81077)", async () => {
await expect(runCli(["node", "openclaw", "foo", "--help"])).rejects.toThrow(
'No built-in command or plugin CLI metadata owns "foo"',
);
expect(startProxyMock).not.toHaveBeenCalled();
expect(tryRouteCliMock).not.toHaveBeenCalled();
expect(buildProgramMock).not.toHaveBeenCalled();
expect(registerPluginCliCommandsFromValidatedConfigMock).not.toHaveBeenCalled();
});
it("rejects unowned command roots even when --version is appended", async () => {
await expect(runCli(["node", "openclaw", "foo", "--version"])).rejects.toThrow(
'No built-in command or plugin CLI metadata owns "foo"',
);
expect(startProxyMock).not.toHaveBeenCalled();
expect(tryRouteCliMock).not.toHaveBeenCalled();
});
it("does not suggest plugins.allow for unknown command roots before proxy startup", async () => {
loadConfigMock.mockReturnValueOnce({
plugins: {

View File

@@ -397,7 +397,6 @@ async function resolveUnownedCliPrimary(params: {
const invocation = resolveCliArgvInvocation(rewriteUpdateFlagArgv(params.argv));
const { primary } = invocation;
if (
invocation.hasHelpOrVersion ||
!primary ||
primary === "help" ||
isReservedNonPluginCommandRoot(primary) ||
@@ -616,6 +615,19 @@ export async function runCli(argv: string[] = process.argv) {
}
}
// Reject unowned command roots before help/version routing, so that
// `openclaw <typo> --help` surfaces the same Unknown command error as
// `openclaw <typo>` instead of silently showing generic top-level help.
// Runs after legitimate precomputed help fast paths so known help commands
// still dispatch normally. See #81077.
{
const config = await readBestEffortCliConfig();
const unownedPrimary = await resolveUnownedCliPrimary({ argv: normalizedArgv, config });
if (unownedPrimary) {
throw new Error(await resolveUnownedCliPrimaryMessage({ primary: unownedPrimary, config }));
}
}
const shouldRunBareRootCrestodian = shouldStartCrestodianForBareRoot(normalizedArgv);
const shouldRunModernOnboardCrestodian = shouldStartCrestodianForModernOnboard(normalizedArgv);
if (shouldRunBareRootCrestodian || shouldRunModernOnboardCrestodian) {