From b799c9e22b47732e4b2091506cb28845822c2c1f Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Mon, 2 Mar 2026 18:59:47 -0500 Subject: [PATCH] CLI: handle root option values in route parsing --- src/cli/argv.test.ts | 5 +++++ src/cli/argv.ts | 2 +- src/cli/route.test.ts | 12 ++++++++++++ src/cli/route.ts | 4 ++-- src/cli/run-main.test.ts | 1 + src/cli/run-main.ts | 4 ++-- 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/cli/argv.test.ts b/src/cli/argv.test.ts index 35fad29a546..665b47c4f60 100644 --- a/src/cli/argv.test.ts +++ b/src/cli/argv.test.ts @@ -181,6 +181,11 @@ describe("argv helpers", () => { argv: ["node", "openclaw"], expected: null, }, + { + name: "skips known root option values", + argv: ["node", "openclaw", "--log-level", "debug", "status"], + expected: "status", + }, ])("returns primary command: $name", ({ argv, expected }) => { expect(getPrimaryCommand(argv)).toBe(expected); }); diff --git a/src/cli/argv.ts b/src/cli/argv.ts index a2f77c15579..ed5938bc76b 100644 --- a/src/cli/argv.ts +++ b/src/cli/argv.ts @@ -219,7 +219,7 @@ function getCommandPathInternal( } export function getPrimaryCommand(argv: string[]): string | null { - const [primary] = getCommandPath(argv, 1); + const [primary] = getCommandPathWithRootOptions(argv, 1); return primary ?? null; } diff --git a/src/cli/route.test.ts b/src/cli/route.test.ts index 6979c4d58ea..c2b2270fd0a 100644 --- a/src/cli/route.test.ts +++ b/src/cli/route.test.ts @@ -69,4 +69,16 @@ describe("tryRouteCli", () => { commandPath: ["status"], }); }); + + it("routes status when root options precede the command", async () => { + await expect(tryRouteCli(["node", "openclaw", "--log-level", "debug", "status"])).resolves.toBe( + true, + ); + + expect(findRoutedCommandMock).toHaveBeenCalledWith(["status"]); + expect(ensureConfigReadyMock).toHaveBeenCalledWith({ + runtime: expect.any(Object), + commandPath: ["status"], + }); + }); }); diff --git a/src/cli/route.ts b/src/cli/route.ts index 2d86eeb036c..b1d7b2851e1 100644 --- a/src/cli/route.ts +++ b/src/cli/route.ts @@ -1,7 +1,7 @@ import { isTruthyEnvValue } from "../infra/env.js"; import { defaultRuntime } from "../runtime.js"; import { VERSION } from "../version.js"; -import { getCommandPath, hasFlag, hasHelpOrVersion } from "./argv.js"; +import { getCommandPathWithRootOptions, hasFlag, hasHelpOrVersion } from "./argv.js"; import { emitCliBanner } from "./banner.js"; import { ensurePluginRegistryLoaded } from "./plugin-registry.js"; import { ensureConfigReady } from "./program/config-guard.js"; @@ -34,7 +34,7 @@ export async function tryRouteCli(argv: string[]): Promise { return false; } - const path = getCommandPath(argv, 2); + const path = getCommandPathWithRootOptions(argv, 2); if (!path[0]) { return false; } diff --git a/src/cli/run-main.test.ts b/src/cli/run-main.test.ts index 0884d05b65e..495a23684d1 100644 --- a/src/cli/run-main.test.ts +++ b/src/cli/run-main.test.ts @@ -114,6 +114,7 @@ describe("shouldEnsureCliPath", () => { it("skips path bootstrap for read-only fast paths", () => { expect(shouldEnsureCliPath(["node", "openclaw", "status"])).toBe(false); + expect(shouldEnsureCliPath(["node", "openclaw", "--log-level", "debug", "status"])).toBe(false); expect(shouldEnsureCliPath(["node", "openclaw", "sessions", "--json"])).toBe(false); expect(shouldEnsureCliPath(["node", "openclaw", "config", "get", "update"])).toBe(false); expect(shouldEnsureCliPath(["node", "openclaw", "models", "status", "--json"])).toBe(false); diff --git a/src/cli/run-main.ts b/src/cli/run-main.ts index 4f78c82bd4d..b902b9197e2 100644 --- a/src/cli/run-main.ts +++ b/src/cli/run-main.ts @@ -8,8 +8,8 @@ import { ensureOpenClawCliOnPath } from "../infra/path-env.js"; import { assertSupportedRuntime } from "../infra/runtime-guard.js"; import { installUnhandledRejectionHandler } from "../infra/unhandled-rejections.js"; import { enableConsoleCapture } from "../logging.js"; -import { getCommandPath, getPrimaryCommand, hasHelpOrVersion } from "./argv.js"; import { applyCliProfileEnv, parseCliProfileArgs } from "./profile.js"; +import { getCommandPathWithRootOptions, getPrimaryCommand, hasHelpOrVersion } from "./argv.js"; import { tryRouteCli } from "./route.js"; import { normalizeWindowsArgv } from "./windows-argv.js"; @@ -46,7 +46,7 @@ export function shouldEnsureCliPath(argv: string[]): boolean { if (hasHelpOrVersion(argv)) { return false; } - const [primary, secondary] = getCommandPath(argv, 2); + const [primary, secondary] = getCommandPathWithRootOptions(argv, 2); if (!primary) { return true; }