Skip to content

fix(cli): reject --agent on models aliases and scan instead of ignoring it - #126761

Closed
ntdatt812 wants to merge 5 commits into
openclaw:mainfrom
ntdatt812:fix/models-agent-scope-guard
Closed

ntdatt812 wants to merge 5 commits into
openclaw:mainfrom
ntdatt812:fix/models-agent-scope-guard

Conversation

@ntdatt812

@ntdatt812 ntdatt812 commented Aug 20, 2026 •

Copy link
Copy Markdown

Closes #126597.

What Problem This Solves

openclaw models aliases {list,add,remove} and openclaw models scan accept --agent <id> from the parent models command and do nothing with it — no scoping, and no validation, so a nonexistent id passes silently:

$ openclaw models --agent totally-bogus-agent-id status
[openclaw] Reason: Unknown agent id "totally-bogus-agent-id". Use "openclaw agents list" to see configured agents.

$ openclaw models --agent totally-bogus-agent-id aliases list
# prints the normal alias list, no error, no effect

Every other --agent-aware models subcommand validates the id, so the flag looks like it scoped the command when it did nothing at all. An operator scoping a command to one agent in a multi-agent fleet gets no feedback that the flag was inert, and a typo'd agent id is never caught.

Why reject rather than wire it through

The issue offered both options, so I checked the downstream before choosing. There is no agent-scoped path to wire --agent to — modelsAliasesListCommand and its siblings take (opts, runtime) and read cfg.agents?.defaults?.models, with no agent parameter anywhere in the chain:

export async function modelsAliasesListCommand(
  opts: { json?: boolean; plain?: boolean },
  runtime: RuntimeEnv,
) {
  const cfg = await loadModelsConfig({ commandName: "models aliases list", runtime });
  const models = cfg.agents?.defaults?.models ?? {};

models set / set-image already guard the same way, for the same reason.

Changes

  • The four subcommands call rejectAgentScopedModelCommand inside withModelsRuntime, so the rejection renders through formatCliOperatorError and exits 1 like any other operator error. (My first revision threw before the wrapper, copying the shape models set uses; ClawSweeper caught that it made the error path worse for exactly these commands, and it was right.)
  • rejectAgentScopedModelWrite → rejectAgentScopedModelCommand: it now covers reads (aliases list, scan --no-probe), so the write-shaped name and its "only updates global model defaults" wording were both inaccurate. The message says "only affects the global model defaults under agents.defaults".
  • commandName is a GlobalOnlyModelsCommand union, so a future global-only subcommand names itself instead of passing a free-form string.
  • docs/cli/models.md listed scan and aliases as commands that "always use the configured default agent". They are now listed with set/set-image as rejecting --agent, plus an upgrade note for the compatibility change.

Evidence

Real CLI, after pnpm build, on this branch (Windows, Node 24):

$ node openclaw.mjs models --agent totally-bogus-agent-id aliases list
openclaw models aliases list does not support --agent; it only affects the global model defaults under agents.defaults. Remove --agent, or run openclaw agents list and set the per-agent model in agent config.
exit=1

$ node openclaw.mjs models --agent totally-bogus-agent-id scan --no-probe --no-input
openclaw models scan does not support --agent; it only affects the global model defaults under agents.defaults. Remove --agent, or run openclaw agents list and set the per-agent model in agent config.
exit=1

$ node openclaw.mjs models aliases list          # unchanged without --agent
Aliases (0):
- none
exit=0

Focused tests and static checks:

$ node scripts/run-vitest.mjs run src/cli/models-cli.test.ts src/cli/models-cli.lazy.test.ts
 Test Files  2 passed (2)
      Tests  46 passed (46)          # 38 before this change

$ npx tsc --noEmit -p tsconfig.json
# 0 errors

$ npx oxfmt --check src/cli/models-cli.ts src/cli/models-cli.runtime.ts src/cli/models-cli.test.ts
All matched files use the correct format.

Mutation-checked. Removing the four guard calls, keeping the tests:

 FAIL  reports parent --agent as an operator error for models 'aliases list'
 FAIL  reports parent --agent as an operator error for models 'aliases add'
 FAIL  reports parent --agent as an operator error for models 'aliases remove'
 FAIL  reports parent --agent as an operator error for models 'scan'
      Tests  4 failed | 40 passed (44)

Exactly the four new cases, and the "still runs without --agent" cases stay green.

Whole src/cli suite, measured on both sides. It has a pre-existing failure surface on Windows, so I ran it against the unpatched tree (git checkout HEAD~1 -- src/cli/) rather than assume. The failing files are identical with and without the patch — 14 files including profile (9), update-cli (40), plugins-cli.install (4) and gateway-backed-exit.process (8). models-cli has zero failures in both runs, and none of those files is touched here.

One note on the build used for the CLI proof: pnpm build completed the compile but its last step, write-cli-startup-metadata, failed rendering help for the browser plugin (dist/extensions/browser/index.js: Cannot read properties of undefined (reading 'join')). That is unrelated to this change and I did not verify whether it also fails on a clean tree; dist/entry.js was produced and the commands above ran against it.

Tests added

The four subcommands assert the formatted operator-error path: the message reaching console.error, naming the subcommand it rejected (so a copy-paste that guards scan while reporting set fails), and exit(1). A second block pins that all four still run normally without --agent, so the guard cannot quietly grow into a blanket refusal.

Two harness details cost a round each and are worth stating for the next person: defaultRuntime.error routes through console.error rather than a direct process.stderr write, and defaultRuntime.exit throws an "unreachable" sentinel once process.exit is mocked (src/runtime.ts:121).

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@openclaw-barnacle openclaw-barnacle Bot added cli CLI command changes size: S triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Aug 20, 2026
@ntdatt812
ntdatt812 force-pushed the fix/models-agent-scope-guard branch from 92ea93d to 43ea027 Compare August 20, 2026 17:41
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Aug 20, 2026
@clawsweeper clawsweeper Bot added merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 20, 2026
@clawsweeper

clawsweeper Bot commented Aug 20, 2026 •

Copy link
Copy Markdown
Contributor

Codex review: found issues before merge. Reviewed August 25, 2026, 5:52 PM ET / 21:52 UTC.

ClawSweeper review

What this changes

The PR rejects inherited --agent on global-only model alias and scan commands, documents the changed behavior, and adds CLI regression tests.

Regression provenance

Possible regression — probable (reproduction; reviewed change). No predecessor PR is attributed.

Merge readiness

⚠️ Needs maintainer review before merge - 6 items remain

Keep open: the guard repairs the silent no-op and has credible CLI proof, but it turns an inherited documented option into an immediate error without a maintainer compatibility decision; the remaining documentation wording for scan is also inaccurate.

Priority: P2
Reviewed head: d82214a5e50b1ff38111d8b70e0bdb2940d9714d
Owner decision: Required. See Decision needed.

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) The repair and its real CLI proof are solid, but a compatibility choice and one inaccurate scope statement remain before merge.
Proof confidence 🐚 platinum hermit (4/6) Sufficient (terminal): The PR body includes after-fix terminal output and exit codes for aliases and scan on a built Windows CLI, plus unchanged no-agent behavior.
Patch quality 🦐 gold shrimp (3/6) 2 actionable review findings remain.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The PR body includes after-fix terminal output and exit codes for aliases and scan on a built Windows CLI, plus unchanged no-agent behavior.
Evidence reviewed 6 items Current-main behavior: Current main still dispatches alias and scan actions without resolving or rejecting the inherited parent option, so the reported behavior remains unfixed there.
PR owner boundary: The branch places the guard inside the existing runtime error wrapper before alias or scan leaf imports, preserving formatted operator-error handling.
No downstream agent target: Alias commands load and update agents.defaults models without an agent parameter; scan only writes defaults when its selected results are applied.
Findings 2 actionable findings [P1] Stage the breaking --agent rejection
[P3] Describe scan's global scope precisely
Security None None.

Live Verification

Command: pnpm openclaw models --agent invalid-review-agent aliases list

Result: FAIL (failed) — step 1 run pnpm openclaw models --agent invalid-review-agent aliases list: previous terminal command was still running after 30 seconds: "pnpm openclaw models --agent invalid-review-agent aliases list"

$ node scripts/run-node.mjs models --agent invalid-review-agent aliases list
[openclaw] Building TypeScript (dist is stale: missing_build_stamp - build stamp missing).
[openclaw] Building bundled plugin assets.
[canvas] build: node scripts/bundle-a2ui.mjs
[log] ‹DIR›/a2ui.bundle.js  chunk │ size: 399.96 kB
[log] ‹DIR›/a2ui-v0.9.bundle.js  chunk │ size: 615.66 kB
[log]
[success] rolldown v1.2.0 Finished in 106.42 ms
[diffs] build: node --import tsx ../../scripts/build-diffs-viewer-runtime.mts curated
[diffs-language-pack] build: node --import tsx ../../scripts/build-diffs-viewer-runtime.mts full
[discord] build: node --import tsx ../../scripts/build-discord-activity-sdk.mts
[tsdown-build] invocation 1/2 finished in 0.7s
node_modules/web-tree-sitter/web-tree-sitter.js (2343:30) [EVAL] Use of direct ˋevalˋ function is strongly discouraged as it poses security risks and may cause
issues with minification.
      ╭─[ node_modules/web-tree-sitter/web-tree-sitter.js:2343:31 ]
      │
 2343 │           ASM_CONSTS[start] = eval(func);
      │                               ──┬─
      │                                 ╰─── Use of direct ˋevalˋ here.
      │
      │ Help: Consider using indirect eval. For more information, check the documentation: https://rolldown.rs/guide/troubleshooting#avoiding-direct-eval
──────╯

node_modules/web-tree-sitter/web-tree-sitter.js (2366:32) [EVAL] Use of direct ˋevalˋ function is strongly discouraged as it poses security risks and may cause
issues with minification.
      ╭─[ node_modules/web-tree-sitter/web-tree-sitter.js:2366:33 ]
      │
 2366 │           moduleExports[name] = eval(func);
      │                                 ──┬─
      │                                   ╰─── Use of direct ˋevalˋ here.
      │
      │ Help: Consider using indirect eval. For more information, check the documentation: https://rolldown.rs/guide/troubleshooting#avoiding-direct-eval
──────╯

node_modules/playwright-core/lib/coreBundle.js (65821:28) [EVAL] Use of direct ˋevalˋ function is strongly discouraged as it poses security risks and may cause
issues with minification.
       ╭─[ node_modules/playwright-core/lib/coreBundle.js:65821:29 ]
       │
 65821 │               const value = eval(ˋ(${expr})ˋ);
       │                             ──┬─
       │                               ╰─── Use of direct ˋevalˋ here.
       │
       │ Help: Consider using indirect eval. For more information, check the documentation: https://rolldown.rs/guide/troubleshooting#avoiding-direct-eval
───────╯

node_modules/playwright-core/lib/coreBundle.js (65828:28) [EVAL] Use of direct ˋevalˋ function is strongly discouraged as it poses security risks and may cause
issues with minification.
       ╭─[ node_modules/playwright-core/lib/coreBundle.js:65828:29 ]
       │
 65828 │               const value = eval(ˋ(${expr})ˋ);
       │                             ──┬─
       │                               ╰─── Use of direct ˋevalˋ here.
       │
       │ Help: Consider using indirect eval. For more information, check the documentation: https://rolldown.rs/guide/troubleshooting#avoiding-direct-eval
───────╯

[tsdown-build] invocation 2/2 finished in 13.9s
[plugin-control-plane-loads] verified 35 built modules with native require and checked doctor closures
runtime-postbuild: 11 phases completed in 4233ms (slowest: built plugin control-plane loads 3490ms)
│
◇

OpenClaw 2026.8.1 (d82214a) — I can grep it, git blame it, and gently roast it—pick your coping mechanism.

openclaw models aliases list does not support --agent; it only affects the global model defaults under agents.defaults. Remove --agent, or run openclaw agents l
ist and set the per-agent model in agent config.
[ELIFECYCLE] Command failed with exit code 1.

Assertions:

  • FAIL expect_output: does not support --agent

How this fits together

The models CLI accepts a parent agent selector and routes subcommands either to an agent-specific operation or to global model defaults. Aliases and scan lack an agent-specific downstream path, so the new guard stops a misleading inherited option before command execution.

flowchart LR
  A[Operator command] --> B[Models CLI parser]
  B --> C{Subcommand scope}
  C -->|Agent-aware| D[Resolve configured agent]
  C -->|Global-only| E[Reject inherited agent option]
  D --> F[Agent model operation]
  E --> G[Formatted operator error]
  C --> H[Global defaults or catalog scan]
Loading

Decision needed

Question Recommendation
Should previously accepted inherited --agent on aliases and scan become an error immediately, or emit a deprecation warning for one stable release first? Warn, then reject: Keep execution global for one stable release but print a clear deprecation warning, then make the option an error in the announced follow-up.

Why: The option was accepted by the parent parser and existing scripts can carry it, while the current reference already says these commands use global behavior; repository policy treats documented CLI flags and upgrade behavior as compatibility-sensitive.

Before merge

  • Stage the breaking `--agent` rejection (P1) - This newly makes existing invocations exit 1 even though the parent option was accepted before. Scripts carrying the inert flag will stop rather than receive a visible migration signal; use the maintainer-selected warning transition or obtain explicit approval for the immediate break. This compatibility concern was visible on the unchanged earlier head but was not previously surfaced as a line finding.
  • Describe scan's global scope precisely (P3) - scan does more than read or write agents.defaults: normal execution resolves OpenRouter credentials and calls the scanner, while metadata-only scan may not write configuration at all. Say that it has no per-agent mode and that any configuration effects are global; this repeats the prior unfixed documentation blocker.
  • Resolve merge risk (P1) - Merging changes four commands from accepting a parent --agent to exiting nonzero, so scripts that retained the inert option will stop until a maintainer chooses an immediate break or a deprecation release.
  • Resolve merge risk (P1) - The bug fix is production net +14 lines; the shared typed guard is a reasonable owner-boundary justification, but it should not grow further while resolving the compatibility decision.
  • Complete next step (P2) - A maintainer must choose the public compatibility transition before the contributor can make the remaining focused change.

Findings

  • [P1] Stage the breaking --agent rejection — src/cli/models-cli.ts:292-295
  • [P3] Describe scan's global scope precisely — docs/cli/models.md:31
Agent review details

Security

None.

PR surface

Source +14, Tests +82, Docs +6. Total +102 across 5 files.

View PR surface stats
Area Files Added Removed Net
Source 2 28 14 +14
Tests 2 88 6 +82
Docs 1 7 1 +6
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 5 123 21 +102

Review metrics

Metric Value Why it matters
Behavioral compatibility change 4 commands change from inherited-option no-op to exit 1 Existing scripts can carry the parent flag even though it never provided agent scope.
Production versus test delta production +14, docs +6, tests +82 net lines The production growth is confined to a shared typed guard and command names; the much larger test delta covers each command path.

Stored data model

Persistent data-model change detected: migration/backfill/repair: docs/cli/models.md. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster

Relationship: fixed_by_candidate
Canonical: #126597
Summary: This PR is the candidate implementation for the reported inherited-option no-op.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge-risk options

Maintainer options:

  1. Use a deprecation release (recommended)
    Accept the inherited option temporarily while warning that it has no agent scope, then reject it after the documented transition.
  2. Accept the immediate script break
    Land the current exit-one behavior as an intentional compatibility change after a maintainer explicitly selects it.

Technical review

Best possible solution:

Describe scan as having no agent-scoped mode and only global configuration effects, then apply the maintainer-selected compatibility policy before landing the rejection.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible: current main registers aliases and scan without consuming the parent option, while the branch’s real CLI transcript shows the proposed after-fix error path.

Is this the best way to solve the issue?

Unclear: rejecting the inert option is the right owner-boundary repair, but the immediate compatibility break versus a warning-release transition requires maintainer direction.

Full review comments:

  • [P1] Stage the breaking --agent rejection — src/cli/models-cli.ts:292-295
    This newly makes existing invocations exit 1 even though the parent option was accepted before. Scripts carrying the inert flag will stop rather than receive a visible migration signal; use the maintainer-selected warning transition or obtain explicit approval for the immediate break. This compatibility concern was visible on the unchanged earlier head but was not previously surfaced as a line finding.
    Confidence: 0.89
    Late finding: first raised on code an earlier review cycle already covered.
  • [P3] Describe scan's global scope precisely — docs/cli/models.md:31
    scan does more than read or write agents.defaults: normal execution resolves OpenRouter credentials and calls the scanner, while metadata-only scan may not write configuration at all. Say that it has no per-agent mode and that any configuration effects are global; this repeats the prior unfixed documentation blocker.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.92

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against e1e2818a6ab0.

Labels

Label changes:

  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🐚 platinum hermit and patch quality is 🦐 gold shrimp.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The PR body includes after-fix terminal output and exit codes for aliases and scan on a built Windows CLI, plus unchanged no-agent behavior.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🦐 gold shrimp, so this older rating label is no longer current.
  • remove status: 👀 ready for maintainer look: Current PR status label is status: ⏳ waiting on author.

Label justifications:

  • P2: This is a bounded CLI correctness repair with limited operator blast radius.
  • merge-risk: 🚨 compatibility: The branch deliberately changes previously accepted parent-option invocations into failures for existing scripts.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🐚 platinum hermit and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (terminal): The PR body includes after-fix terminal output and exit codes for aliases and scan on a built Windows CLI, plus unchanged no-agent behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal output and exit codes for aliases and scan on a built Windows CLI, plus unchanged no-agent behavior.

Evidence

What I checked:

  • Current-main behavior: Current main still dispatches alias and scan actions without resolving or rejecting the inherited parent option, so the reported behavior remains unfixed there. (src/cli/models-cli.ts:172, e1e2818a6ab0)
  • PR owner boundary: The branch places the guard inside the existing runtime error wrapper before alias or scan leaf imports, preserving formatted operator-error handling. (src/cli/models-cli.ts:292, d82214a5e50b)
  • No downstream agent target: Alias commands load and update agents.defaults models without an agent parameter; scan only writes defaults when its selected results are applied. (src/commands/models/aliases.ts:17, d82214a5e50b)
  • Unfixed documentation finding: The changed reference says scan only reads and writes agents.defaults, but scan can resolve an OpenRouter credential and perform catalog/probe I/O before any optional global configuration write. Public docs: docs/cli/models.md. (docs/cli/models.md:31, d82214a5e50b)
  • Regression coverage and real proof: The branch tests formatted rejection and unscoped leaf dispatch for all four commands; the PR body also records after-fix Windows CLI output and exit codes. (src/cli/models-cli.test.ts:475, d82214a5e50b)
  • History and routing: History shows inherited nested-option support was introduced by Josh Lehman and the adjacent multi-agent CLI path was recently maintained by Peter Steinberger. (src/cli/models-cli.ts:48, 56798ae08505)

Likely related people:

  • Peter Steinberger: Recent merged work addressed adjacent multi-agent models CLI behavior and he has substantial history on this command surface. (role: recent CLI maintainer; confidence: high; commits: 1ef2ebf11468, 55aa0480ebe0; files: src/cli/models-cli.ts, src/cli/models-cli.runtime.ts)
  • Josh Lehman: Commit 56798ae added inherited flags after nested subcommands, directly relevant to the option this PR now rejects. (role: introduced inherited-option behavior; confidence: high; commits: 56798ae08505; files: src/cli/models-cli.ts, src/cli/cli-utils.ts)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Obtain a maintainer decision on immediate rejection versus a warning-release transition.
  • Correct the scan scope statement in the CLI reference and keep the error text consistent with it.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (9 earlier review cycles; latest 8 shown)
  • reviewed 2026-08-20T18:32:19.784Z sha 779cfe0e4caf825aee02b58b4d726a219f25d18b :: needs maintainer review before merge. :: none
  • reviewed 2026-08-20T18:55:06.877Z sha c97c6f6b89cc0d645af90e66c52c3cb0ebeb2358 :: needs changes before merge. :: [P2] Isolate each no-agent command assertion
  • reviewed 2026-08-21T06:30:20.044Z sha 5904b8467c4a2756c308c0607f98515407d85616 :: needs maintainer review before merge. :: none
  • reviewed 2026-08-21T07:39:27.198Z sha 16d7f5c5dff43ae0d6d65fe26c03eeb2f333dda1 :: found issues before merge. :: [P3] Use American spelling in the upgrade note
  • reviewed 2026-08-21T08:11:10.436Z sha d82214a :: needs maintainer review before merge. :: none
  • reviewed 2026-08-22T12:07:22.988Z sha d82214a :: needs maintainer review before merge. :: none
  • reviewed 2026-08-23T14:42:04.644Z sha d82214a :: found issues before merge. :: [P3] Describe scan's global scope precisely
  • reviewed 2026-08-23T22:01:48.995Z sha d82214a :: found issues before merge. :: [P3] Describe scan's global scope precisely

@openclaw-barnacle openclaw-barnacle Bot added the docs Improvements or additions to documentation label Aug 20, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Aug 20, 2026
@ntdatt812
ntdatt812 force-pushed the fix/models-agent-scope-guard branch 2 times, most recently from d7fd7c7 to c97c6f6 Compare August 20, 2026 18:49
@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Aug 20, 2026
@ntdatt812
ntdatt812 force-pushed the fix/models-agent-scope-guard branch from c97c6f6 to 5904b84 Compare August 21, 2026 06:25
@ntdatt812

Copy link
Copy Markdown
Author

The P2 finding is correct, and it was worse than a latent risk — the assertion was already vacuous, not just fragile. I checked before changing anything.

beforeEach clears an explicit list of spies and noopAsync is not in it, and there is no clearMocks anywhere under test/vitest/, so the default false applies. The proof is the mutation you implied: block models aliases remove from ever reaching its leaf command, keep the tests as they were.

$ node scripts/run-vitest.mjs run src/cli/models-cli.test.ts
 Test Files  1 passed (1)
      Tests  44 passed (44)

Green, with the leaf provably never invoked. The earlier row's call satisfied it.

Fixed by giving aliases list/add/remove and scan their own spies, clearing them in beforeEach, and asserting toHaveBeenCalledTimes(1) on the row's own leaf rather than toHaveBeenCalled() on a shared one — so a double dispatch is caught as well as a missing one. The rejection table asserts the same per-row leaf was not called. Both tables are now driven from one list, so a rejection row and an unscoped row cannot drift onto different commands.

Same mutation after the fix:

 FAIL  src/cli/models-cli.test.ts > still runs models 'aliases remove' without --agent
AssertionError: expected "vi.fn()" to be called 1 times, but got 0 times
      Tests  1 failed | 43 passed (44)

One failure, on the row whose leaf I blocked.

Guard-removal mutation still behaves — deleting all four rejectAgentScopedModelCommand calls fails exactly the four rejection rows and leaves the unscoped rows green.

Clean run on the pushed head:

$ node scripts/run-vitest.mjs run src/cli/models-cli.test.ts src/cli/models-cli.lazy.test.ts
 Test Files  2 passed (2)
      Tests  46 passed (46)

$ npx oxfmt --check src/cli/models-cli.test.ts
All matched files use the correct format.

$ git diff --check
(clean)

No production code changed in this push — the diff is src/cli/models-cli.test.ts only.

On merge-risk: 🚨 compatibility: agreed and intended. A script passing --agent to these four today gets silent no-op scoping, which is the defect; the docs note added in the previous push tells operators to drop the inert flag. Happy to soften it to a deprecation warning for a release instead if you would rather not break those scripts in one step — say which you prefer and I will push it.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event exact_review_queue).
Result: when the review finishes, ClawSweeper will create the durable review comment if needed or update the existing comment in place.

Re-review progress:

@clawsweeper clawsweeper Bot removed the rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. label Aug 21, 2026
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Aug 21, 2026
@ntdatt812
ntdatt812 force-pushed the fix/models-agent-scope-guard branch from 5904b84 to 16d7f5c Compare August 21, 2026 07:32
…ng it

`openclaw models aliases {list,add,remove}` and `openclaw models scan` accepted
`--agent <id>` from the parent `models` command and did nothing with it — no
scoping, and no validation, so a nonexistent id passed silently:

    openclaw models --agent totally-bogus-agent-id status
    # Unknown agent id "totally-bogus-agent-id".

    openclaw models --agent totally-bogus-agent-id aliases list
    # prints the normal alias list, no error

Every other `--agent`-aware `models` subcommand validates the id, so the flag
looks like it scoped the command when it did nothing at all (openclaw#126597).

Rejecting is the right shape here rather than wiring `--agent` through, because
there is no agent-scoped path to wire it to: `modelsAliasesListCommand` and its
siblings take `(opts, runtime)` and read `cfg.agents?.defaults?.models` with no
agent parameter anywhere in the chain. `models set` / `set-image` already guard
the same way for the same reason.

Two things had to change with the guard rather than around it:

- It is no longer write-only. `aliases list` and `scan --no-probe` are reads, so
  `rejectAgentScopedModelWrite` is renamed `rejectAgentScopedModelCommand`, and
  its message says the command "only affects the global model defaults under
  agents.defaults" instead of "only updates" them.
- `commandName` is now a `GlobalOnlyModelsCommand` union, so a new global-only
  subcommand has to name itself rather than pass a free-form string.

Tests extend the existing parent-`--agent` rejection table with the four
subcommands, assert the message names the one that was rejected, and pin that
all four still run normally without `--agent` — so the guard cannot quietly grow
into a blanket refusal.

Verified on Windows, Node 24:
- src/cli/models-cli.test.ts + models-cli.lazy.test.ts   48 passed (was 38)
- test/scripts/write-cli-startup-metadata.test.ts        16 passed, 5 skipped
- tsc --noEmit                                           0 errors
- oxfmt --check                                          clean

Mutation-checked: removing the four guard calls fails exactly the six new
rejection cases, and leaves the "still runs without --agent" cases green.

Closes openclaw#126597
…cument it

ClawSweeper review on openclaw#126761. Two findings, both mine to fix.

The guards threw before `runModelsCommand`, because I copied the shape `models
set` uses. The prior alias and scan actions ran *inside* `withModelsRuntime`, so
that move made the error path worse for exactly the commands this PR touches:
`runCommandWithRuntime` catches, renders through `formatCliOperatorError` and
exits 1, and a throw outside it reaches the CLI parser's unexpected-error path
instead. An ordinary unsupported-option mistake was being presented as a runtime
failure.

The guards are back inside `withModelsRuntime`, which also restores the original
action shape, so the diff is now smaller than it was.

The tests had to follow, and that is the evidence the finding was right: the
`rejects.toThrow` assertions went red once the error stopped propagating. They
now assert the formatted path instead — the message reaching `console.error`,
naming the subcommand it rejected, and `exit(1)`. Two details cost a round each
and are worth recording: `defaultRuntime.error` routes through `console.error`
rather than a direct `process.stderr` write, and `defaultRuntime.exit` throws an
"unreachable" sentinel once `process.exit` is mocked (`src/runtime.ts:121`).

`docs/cli/models.md` still said `scan` and `aliases` "always use the configured
default agent". They are listed with `set`/`set-image` as rejecting `--agent`
now, and an upgrade note states the compatibility change: scripts passing
`--agent` to these subcommands will fail, and the behaviour they were getting was
always the global default, so dropping the flag does not change what the command
does.

Verified on Windows, Node 24:
- src/cli/models-cli.test.ts + models-cli.lazy.test.ts   46 passed (38 before)
- tsc --noEmit                                           0 errors
- oxfmt --check                                          clean
- real CLI after `pnpm build`:

    $ node openclaw.mjs models --agent totally-bogus-agent-id aliases list
    openclaw models aliases list does not support --agent; it only affects the
    global model defaults under agents.defaults. ...
    exit=1

    $ node openclaw.mjs models aliases list
    Aliases (0):
    - none
    exit=0

Mutation-checked: removing the four guard calls fails exactly the four new
operator-error cases.
`pnpm format:check` covers docs as well as source, and I had only run oxfmt on
the TypeScript files. `check-docs` failed on `docs/cli/models.md`; the reflow is
whitespace only.

`check-lint` in the same run was cancelled rather than failed once check-docs
went red.
The four "still runs without --agent" rows all asserted the same shared
`noopAsync` spy, and nothing cleared it between rows. Once the first row
called it the assertion was satisfied for every later row, so a
regression that blocked a later leaf still passed.

Verified: blocking `models aliases remove` from ever reaching its leaf
command left all 44 tests green. Each subcommand now owns a spy, cleared
in `beforeEach`, and each row asserts its own leaf ran exactly once --
the same mutation now fails, and only on that row.

Both tables are driven from one list, so a rejected case and an
unscoped case can no longer describe different commands.
@ntdatt812
ntdatt812 force-pushed the fix/models-agent-scope-guard branch from 16d7f5c to ef85a4e Compare August 21, 2026 07:59
AGENTS.md requires American spelling, and docs/ already uses `behavior`
in 331 files against `behaviour` in 2.
@ntdatt812

Copy link
Copy Markdown
Author

P3 fixed in d82214a5e5 — behaviour → behavior in the upgrade note. Checked the rule before changing it rather than taking the finding on faith: AGENTS.md:289 requires American spelling, and docs/ already uses behavior in 331 files against behaviour in 2.

CI is green on that head: 142 passed, 0 failed.

One thing worth flagging, since it cost me some time and may be useful to you. Before this push, checks-node-compact-large-7 failed twice on this branch, on two different bases:

FAIL unit-src src/entry.run-main.test.ts > entry run-main boundary
     > keeps expected conditions at exit 1 without crash framing
AssertionError: expected [] to deeply equal [ Array(1) ]

process.exitCode was 1 as expected; only console.error was never called. I did not report it as a flake at the time, because the surrounding data pointed the other way — that job was green on the last 8 main runs (including 4 where CI failed for other reasons) and on 6 other open PRs, while red 2/2 here.

It is a flake, and the third run settles it: the only change between the red head and the green one is the single word above, in a Markdown file, which cannot affect that test. Nothing in this PR touches src/entry.ts, src/runtime.ts, or the plugin registry — the diff is docs/cli/models.md plus four src/cli/models-cli.* files — and the failing job contains only the core-unit-src-security-2 shard, which does not include this PR's test file.

The shape of the failure suggests an ordering-dependent leak: something in that shard replaces console.error and does not restore it. resolveVitestIsolation() returns false and the shared setup's afterEach does not call restoreAllMocks, so a spy left installed by one file stays installed for the rest of the worker. I have not identified which file, and I am not claiming one — happy to open a separate issue with the run links if that is useful, rather than expanding the scope of this PR.

Still open from your side is the compatibility question in the review — immediate rejection as it stands, or a deprecation warning for a release first. I have no preference and can push either.

@clawsweeper clawsweeper Bot added clawsweeper-recovery-stuck rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. clawsweeper-recovery-stuck labels Aug 25, 2026
@vincentkoc vincentkoc self-assigned this Aug 27, 2026
@steipete

Copy link
Copy Markdown
Contributor

Thanks @ntdatt812 for the reproduction, implementation, and careful test isolation work. Closing this as superseded by #126864, merged as 93573e8f. Its merge is an ancestor of the main checkout verified here, bcb5403d. The linked issue #126597 is already closed.

I independently checked the complete patch, current command registration, shared option resolver, aliases/scan owners, error boundary, Commander 15 source, and neighboring command semantics. Current main rejects inherited --agent before dispatch for aliases list/add/remove and scan. #130739 additionally rejects an explicitly empty value. The current scope-neutral message also avoids implying that a metadata-only scan writes agents.defaults.

Fresh proof on the built main CLI, Node 26.7.0, with isolated HOME/state/config:

  • Ran all four commands with models --agent does-not-exist ... and models --agent '' ...: 8/8 exited 1, named the rejected command, and left the fixture config byte-identical. Scan used --no-probe --no-input; no model call was made.
  • Ran unscoped models aliases list --json, models aliases add qa-new anthropic/claude-sonnet-4-6, and models aliases remove qa-new: 3/3 exited 0, with the listed alias and actual add/remove config results verified.
  • OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/run-vitest.mjs src/cli/models-cli.test.ts src/cli/models-cli.lazy.test.ts: 2 files, 77 tests passed. The existing table covers seven global commands, nonempty/empty rejection without leaf dispatch, and exactly-once dispatch without the flag.

There is a presentation difference: this PR catches the guard inside the local command wrapper, whereas main's top-level CLI boundary prints the reason with generic startup/help guidance. The real CLI still exits 1 and performs no mutation; there is no unhandled rejection or remaining silent-ignore defect here. We should retain main's canonical guard instead of applying the older duplicate implementation.

This is proof of fixed behavior on main, not a claim that a stable release contains it. Per-agent fallbacks/image-fallbacks are a distinct surface tracked by #106346 / #106414 and are not being closed by this decision.

@steipete steipete closed this Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes docs Improvements or additions to documentation merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. size: S status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

models aliases/scan silently ignore --agent, including invalid agent ids (no validation, no effect)

3 participants