From 78bfcd2c2e24a758f4860bd0c8a190df1432df96 Mon Sep 17 00:00:00 2001 From: juyaohuidt Date: Mon, 13 Jul 2026 15:53:59 +0800 Subject: [PATCH] fix(cli): preserve literal --update subcommand args (#106144) Co-authored-by: xydt-juyaohui --- src/cli/run-main-policy.ts | 30 ++++++++++++++++++++++++----- src/cli/run-main.test.ts | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/cli/run-main-policy.ts b/src/cli/run-main-policy.ts index 30046cc2fc43..bfd57512a18d 100644 --- a/src/cli/run-main-policy.ts +++ b/src/cli/run-main-policy.ts @@ -4,6 +4,7 @@ import { normalizeOptionalLowercaseString, } from "@openclaw/normalization-core/string-coerce"; import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { consumeRootOptionToken, FLAG_TERMINATOR } from "../infra/cli-root-options.js"; import { resolveManifestCommandAliasOwnerInRegistry, resolveManifestToolOwnerInRegistry, @@ -37,14 +38,33 @@ function isBareParentDefaultHelpArgv(argv: string[]): boolean { export function rewriteUpdateFlagArgv(argv: string[]): string[] { // Preserve the old root --update spelling by rewriting before Commander registration. - const index = argv.indexOf("--update"); - if (index === -1) { + // Only rewrite --update while scanning the root-option prefix; once a command + // or `--` appears, later --update tokens belong to that command's arguments. + const updateIndex = argv.indexOf("--update"); + if (updateIndex === -1) { return argv; } - const next = [...argv]; - next.splice(index, 1, "update"); - return next; + for (let i = 2; i < argv.length; i++) { + const arg = argv[i]; + if (!arg || arg === FLAG_TERMINATOR) { + return argv; + } + if (i === updateIndex) { + const next = [...argv]; + next.splice(updateIndex, 1, "update"); + return next; + } + const consumed = consumeRootOptionToken(argv, i); + if (consumed > 0) { + i += consumed - 1; + continue; + } + if (!arg.startsWith("-")) { + return argv; + } + } + return argv; } export function shouldEnsureCliPath(argv: string[]): boolean { diff --git a/src/cli/run-main.test.ts b/src/cli/run-main.test.ts index 1139a55172f6..99ac9a0d29c0 100644 --- a/src/cli/run-main.test.ts +++ b/src/cli/run-main.test.ts @@ -130,6 +130,45 @@ describe("rewriteUpdateFlagArgv", () => { "--json", ]); }); + + it("does not rewrite --update after -- positional terminator", () => { + expect( + rewriteUpdateFlagArgv(["node", "entry.js", "config", "set", "foo", "--", "--update"]), + ).toEqual(["node", "entry.js", "config", "set", "foo", "--", "--update"]); + }); + + it("does not rewrite --update when a subcommand appears before it", () => { + expect( + rewriteUpdateFlagArgv(["node", "entry.js", "config", "set", "update.channel", "--update"]), + ).toEqual(["node", "entry.js", "config", "set", "update.channel", "--update"]); + }); + + it("does not rewrite --update that appears as a value after a subcommand", () => { + expect(rewriteUpdateFlagArgv(["node", "entry.js", "config", "set", "foo", "--update"])).toEqual( + ["node", "entry.js", "config", "set", "foo", "--update"], + ); + }); + + it("rewrites --update when it appears before any subcommand", () => { + expect(rewriteUpdateFlagArgv(["node", "entry.js", "--update", "config", "set", "foo"])).toEqual( + ["node", "entry.js", "update", "config", "set", "foo"], + ); + }); + + it("rewrites --update after root boolean flags", () => { + expect(rewriteUpdateFlagArgv(["node", "entry.js", "--no-color", "--update"])).toEqual([ + "node", + "entry.js", + "--no-color", + "update", + ]); + }); + + it("does not skip root boolean flag followers as option values", () => { + expect(rewriteUpdateFlagArgv(["node", "entry.js", "--no-color", "status", "--update"])).toEqual( + ["node", "entry.js", "--no-color", "status", "--update"], + ); + }); }); describe("shouldEnsureCliPath", () => {