fix(status): usage follows session model after switch#93384
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 24, 2026, 10:42 AM ET / 14:42 UTC. Summary PR surface: Source +39, Tests +384. Total +423 across 4 files. Reproducibility: yes. Source-level reproduction is high-confidence: current main can receive selected OpenAI overrides plus stale DeepSeek runtime fields, and the status usage provider currently follows active runtime state; I did not run tests or live Telegram in this read-only review. Review metrics: none identified. Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Mantis proof suggestion Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land this or an equivalent focused status routing fix for #93322, and leave the broader shared resolver work in #87957 open for maintainer sequencing. Do we have a high-confidence way to reproduce the issue? Yes. Source-level reproduction is high-confidence: current main can receive selected OpenAI overrides plus stale DeepSeek runtime fields, and the status usage provider currently follows active runtime state; I did not run tests or live Telegram in this read-only review. Is this the best way to solve the issue? Yes for the narrow bug. The PR moves the status lookup boundary to the selected session model and keeps active-runtime behavior only for explicit fallback/runtime-alias provenance; the broader resolver issue remains the safer place for cross-surface cleanup. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against d83cd282c602. Label changesLabel justifications:
Evidence reviewedPR surface: Source +39, Tests +384. Total +423 across 4 files. View PR surface stats
Acceptance criteria:
What I checked:
Likely related people:
What the crustacean ranks mean
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. How this review workflow works
|
8229509 to
03981e6
Compare
|
A potential solution is to modify api. What do you think? |
936dcb8 to
3a98a28
Compare
|
@obviyus quick clarification after the rebase/squash:
I updated the tests to match the new |
|
Landed via rebase onto main.
Thanks @zhangguiping-xydt! |
|
@obviyus Thank you! You're amazing. 😄 Really appreciate you taking the time to review and land this. It makes contributing to OpenClaw a great experience~ |
Summary
What problem does this PR solve?
Fixes an issue where a session pinned with
/model openai/gpt-5.5could show OpenAI as the selected model while/statusstill resolved usage, auth, harness, and context metadata through the configured default DeepSeek provider. It also fixes the stale-runtime path where persisted runtime state can still reference DeepSeek after a manual session model switch; usage now follows the selected session model unless the active runtime is authoritative through explicit automatic model handoff provenance, an existing active fallback notice, or an equivalent runtime alias. Active fallback notices now also keep the live runtime context window when the fallback model is not resolvable from config, instead of falling back to a stale selected-model context window. The root cause was that status lookup treated the configured or stale active provider/model as the source for provider-specific metadata after the session had a selected-model override; this PR moves the resolver boundary to the selected session model and explicit runtime provenance instead of patching the rendered status text downstream.Why does this matter now?
This PR was already carrying the selected-model status routing fix, and the first blocker was introduced by this PR's new regression fixture: the OpenAI provider config used by the provider-qualified override test omitted the required
ModelProviderConfig.baseUrl. After that was fixed, ClawSweeper identified one remaining author-side gap:/statususage could still query the stale active runtime provider whensessionEntry.modelProvider/modellagged behind a user-pinned session model.What is the intended outcome?
Root-cause fix.
/statusshould keep the configured default visible for new or unpinned sessions, but provider-specific lookup for a pinned session should use the selected provider/model. If active runtime differs because of a real automatic model handoff, an existing active runtime-handoff notice, or an equivalent runtime alias, status can still use active-runtime usage/auth and context metadata. If active runtime differs only because stored runtime state is stale after a manual switch, usage follows the selected session provider. When an active runtime-handoff notice points at a runtime model that is not in config,/statusnow trusts the live runtime context snapshot instead of a stale selected-model context window. The typecheck blocker is fixed at the canonical fixture boundary by giving the new OpenAI provider fixture the same requiredbaseUrlshape used by the existing OpenAI/Codex provider config, not by weakening types or changing runtime behavior.What is intentionally out of scope?
Out of scope: changing model selection semantics, provider credentials, config defaults, migrations, automatic runtime handoff behavior, external usage APIs, schema shape, or compatibility defaults. This update does not add a new public contract; it keeps the existing public contract surface for session overrides and model provider config intact.
What does success look like?
A pinned direct session can run
/model openai/gpt-5.5and then/status; the status card showsConfigured default: deepseek/deepseek-v4-flash,Session selected: openai/gpt-5.5, OpenAI context metadata, and no DeepSeek balance text. A stale runtime snapshot with selected OpenAI and active DeepSeek also loads usage foropenai, notdeepseek. An unresolved active fallback notice with livecontextTokens: 123456and stale persisted selected-windowcontextTokens: 1048576rendersContext: 49k/123k, notContext: 49k/1.0m.pnpm check:test-typespasses on the current head.What should reviewers focus on?
Review the source-of-truth boundary: selected session model resolution should happen before status usage/auth/context lookup, not as a renderer-only post-process. Also review the stale-runtime distinction: active-runtime usage and context metadata are preserved for explicit automatic handoff provenance, active fallback notices, or equivalent runtime aliases, while manual selected-model overrides are not overwritten by stale
sessionEntry.modelProvider/modelstate. The only config fixture correction is the requiredbaseUrlon the OpenAI test provider. Related #93829 also touches status model/provider resolution; this update keeps #93384 scoped to the existing #93322 selected-session status routing path and does not broaden the resolver contract.Linked context
Which issue does this close?
Closes #93322
Which issues, PRs, or discussions are related?
Related #93384
Related #93829 (overlapping open PR noted; this PR stays scoped to #93322's selected-session status routing fix and avoids broad resolver-contract changes).
Was this requested by a maintainer or owner?
ClawSweeper flagged the status routing risk, stale-runtime usage gap, CI/typecheck state, and optional visible
/modelthen/statusproof; this update addresses those author-side items.Real behavior proof (required for external PRs)
Behavior or issue addressed:
/statusnow keeps the configured default line on DeepSeek while resolving the session-selected model, context metadata, and usage/provider decision from OpenAI after a session is pinned toopenai/gpt-5.5.Real environment tested: Existing before-fix Telegram direct session evidence from [Bug]: /status usage should follow session-selected model after /model switch #93322 on
OpenClaw 2026.6.6; retained after-fix Node status-renderer proof against the patched source; and current-head after-fix qa-channel proof using qa-channel + qa-lab bus + real OpenClaw gateway child + mock-openai provider with configured defaultdeepseek/deepseek-v4-flashand session selectionopenai/gpt-5.5.Exact steps or command run after this patch: Retained renderer proof:
OPENAI_API_KEY= DEEPSEEK_API_KEY= node --import tsx --input-type=module <status-renderer proof script>. Current channel proof:daily_fix run-validation --name qa-channel-model-status-proof-round2 -- pnpm exec tsx <qa-channel-model-status-proof.ts>.Evidence after fix (screenshot, recording, terminal capture, console output, redacted runtime log, linked artifact, or copied live output):
Retained renderer proof excerpt from the existing PR body:
Observed result after fix: current-head after-fix proof shows
Session selected: openai/gpt-5.5, keepsConfigured default: deepseek/deepseek-v4-flash, resolves OpenAI context metadata as/258k, and verifies DeepSeek balance text is not shown for the OpenAI-selected session.What was not tested: after-fix proof was not rerun in native Telegram Desktop; the original Telegram direct session report remains the before-fix evidence, and the current qa-channel run exercises the channel plugin boundary with a deterministic transcript and real gateway child.
Proof limitations or environment constraints: the new provider is
mock-openai, so it proves routing and visible status selection without spending live provider quota. The retained renderer proof intentionally avoids a live external OpenAI balance request; regression tests separately assert the provider usage loader receivesopenairather thandeepseek, including when runtime state is stale.Before evidence (optional but encouraged):
That
Balance ¥22.75was the DeepSeek provider balance while the same status card showed the session model as OpenAI.Tests and validation
Which commands did you run?
daily_fix run-validation --name status-requested-focused-run-vitest -- node scripts/run-vitest.mjs src/auto-reply/reply/commands-status.test.ts -t "uses the session-selected model provider for /status usage"— passed; 1 test passed.daily_fix run-validation --name status-requested-run-vitest-suite -- node scripts/run-vitest.mjs src/auto-reply/reply/commands-status.test.ts src/agents/openclaw-tools.session-status.test.ts— passed; 2 Vitest shards passed, 93 tests passed.daily_fix run-validation --name check-test-types-round2 -- pnpm check:test-types— passed.daily_fix run-validation --name qa-channel-model-status-proof-round2 -- pnpm exec tsx <qa-channel-model-status-proof.ts>— passed; drives/model openai/gpt-5.5then/statusthrough qa-channel and a real gateway child.daily_fix run-validation --name ci-boundary-no-extension-test-core-imports -- pnpm run lint:plugins:no-extension-test-core-imports— passed after syncing current main; confirms the prior Feishu extension boundary blocker is no longer present on the updated branch.daily_fix run-validation --name ci-core-support-boundary-isolated-tmp -- env TMPDIR=<isolated task temp> NODE_OPTIONS=--max-old-space-size=8192 OPENCLAW_VITEST_MAX_WORKERS=2 node scripts/run-vitest.mjs run --config test/vitest/vitest.full-core-support-boundary.config.ts— passed; 12 test files and 97 tests passed.daily_fix run-validation --name ci-core-tooling-test-projects -- env NODE_OPTIONS=--max-old-space-size=8192 OPENCLAW_VITEST_MAX_WORKERS=2 node scripts/run-vitest.mjs run --config test/vitest/vitest.tooling.config.ts src/scripts/test-projects.test.ts --reporter=verbose— passed; 83 tests passed.daily_fix run-validation --name focused-status-regression -- env NODE_OPTIONS=--max-old-space-size=8192 OPENCLAW_VITEST_MAX_WORKERS=2 node scripts/run-vitest.mjs run --config test/vitest/vitest.auto-reply.config.ts src/auto-reply/reply/commands-status.test.ts --reporter=verbose— passed; 33 status regression tests passed, including selected-provider and stale-runtime cases.daily_fix run-validation --name ci-core-tooling-cross-os-release-checks -- env NODE_OPTIONS=--max-old-space-size=8192 OPENCLAW_VITEST_MAX_WORKERS=2 node scripts/run-vitest.mjs run --config test/vitest/vitest.tooling.config.ts test/scripts/openclaw-cross-os-release-checks.test.ts --reporter=verbose— passed; 95 process-cleanup and cross-OS release-check tests passed.daily_fix run-validation --name legacy-fallback-usage-green-typesafe -- env NODE_OPTIONS=--max-old-space-size=8192 OPENCLAW_VITEST_MAX_WORKERS=2 node scripts/run-vitest.mjs run --config test/vitest/vitest.auto-reply.config.ts src/auto-reply/reply/commands-status.test.ts -t "uses active fallback provider usage for legacy fallback notices" --reporter=verbose— passed; the legacy runtime-handoff notice case now keepsFallback: minimax-portal/MiniMax-M2.7, uses the active MiniMax context window, and loads usage throughminimax.daily_fix run-validation --name check-test-types-after-fallback-fix -- pnpm check:test-types— passed after the legacy fallback-notice usage guard was added.daily_fix run-validation --name focused-status-regression-after-fallback-fix-typesafe -- env NODE_OPTIONS=--max-old-space-size=8192 OPENCLAW_VITEST_MAX_WORKERS=2 node scripts/run-vitest.mjs run --config test/vitest/vitest.auto-reply.config.ts src/auto-reply/reply/commands-status.test.ts --reporter=verbose— passed; 34 status regression tests passed, including selected-provider, stale-runtime, and legacy fallback-notice usage routing cases.daily_fix run-validation --name focused-status-regression-after-sync -- env NODE_OPTIONS=--max-old-space-size=8192 OPENCLAW_VITEST_MAX_WORKERS=2 pnpm exec node scripts/test-projects.mjs src/auto-reply/reply/commands-status.test.ts -- --reporter=verbose— passed after syncing current main; 1 auto-reply Vitest shard passed, 34 status regression tests passed.daily_fix run-validation --name fallback-context-status-regression -- bash -lc 'OPENCLAW_HEAVY_CHECK_LOCK_SCOPE=worktree node scripts/run-vitest.mjs src/auto-reply/reply/commands-status.test.ts && OPENCLAW_HEAVY_CHECK_LOCK_SCOPE=worktree node scripts/run-vitest.mjs src/status/status-text.test.ts src/status/status-message.test.ts src/auto-reply/status.test.ts'— passed; 35 status command tests, 2 status-text tests, 5 status-message tests, and 84 auto-reply status tests passed.What regression coverage was added or updated?
Regression coverage was added in
commands-status.test.tsfor selected-provider usage/auth/context lookup so/statusdoes not keep querying DeepSeek after the session is pinned to OpenAI. The scenarios lock in separateproviderOverride: "openai"+modelOverride: "gpt-5.5", provider-qualifiedmodelOverride: "openai/gpt-5.5", stale-runtime state where the selected session provider is OpenAI butsessionEntry.modelProvider/modelstill points at DeepSeek, legacy active fallback usage routing, and unresolved active fallback context routing where the live runtime window must win over a stale persisted selected-model window.What failed before this fix, if known?
The PR-introduced typecheck blocker failed because the new OpenAI provider fixture did not satisfy the
ModelProviderConfigcontract:baseUrlis required. The original behavior regression was that a selected OpenAI session could still show DeepSeek usage/balance data in/status. The stale-runtime regression test also failed before the latest status routing fix because/statusstill renderedUsage: Balance ¥42.50and querieddeepseekwhen the selected session model wasopenai/gpt-5.5. The unresolved active fallback regression failed before the final context fix because/statusrenderedContext: 49k/1.0mfrom stale persisted selected-model state instead of the live runtimeContext: 49k/123ksnapshot.If no test was added, why not?
Not applicable. Focused regression tests were added, and the smallest reliable guardrail is the status command test because it reaches the resolver, usage/auth/context lookup, and rendered status output without broadening provider or config behavior.
Risk checklist
Did user-visible behavior change? (
Yes/No)Yes.
/statusnow reports and looks up session-selected model details for pinned sessions while still showing the configured default for unpinned/new-session context.Did config, environment, or migration behavior change? (
Yes/No)No. The public config/default/compatibility contract is unchanged; the test fixture now includes the required OpenAI
baseUrl, and no migration, default, or backward-compatibility behavior changed.Did security, auth, secrets, network, or tool execution behavior change? (
Yes/No)Yes, narrowly in status lookup routing: auth and usage labels are resolved against the selected provider for a pinned session unless active runtime is authoritative through automatic handoff provenance or equivalent runtime alias. This does not add credentials, secrets, migrations, tools, or new network surfaces.
What is the highest-risk area?
Pinned-session status could query or display the wrong provider if selected/default/active model references are resolved incorrectly. The contract surface is provider routing for status metadata, not model execution or stored config schema.
How is that risk mitigated?
The root-cause boundary is covered by focused regression tests for both override shapes, stale runtime state, active fallback usage routing, and unresolved active fallback context routing. The retained renderer proof shows the selected OpenAI status output, and the current-head qa-channel proof shows the visible
/modelthen/statustranscript using the selected OpenAI model while the configured DeepSeek default remains visible. Patch quality warnings are addressed by keeping the diff scoped to resolver/status lookup and the required test fixture config, rather than adding downstream guards, compatibility branches, or renderer string filtering.Current review state
What is the next action?
High confidence: ready for maintainer review after the updated branch and PR body are submitted.
What is still waiting on author, maintainer, CI, or external proof?
Author-side typecheck, stale-runtime usage, legacy runtime-handoff usage, unresolved fallback runtime context, ClawSweeper review, and proof items are addressed locally. The current-head CI failures investigated here are in unrelated test-infrastructure shards, not in the status files changed by this PR; remote checks need to rerun and settle on the updated branch. The native Telegram Desktop proof remains external-environment limited; the deterministic qa-channel proof and status regression suite cover the fixed
/modelthen/statusbehavior.Which bot or reviewer comments were addressed?
cli-runner.spawn.test.ts,test-projects.test.ts,bench-gateway-restart.test.ts, andresolve-openclaw-ref.test.ts), while the synced status regression suite passed on this branch./statusno longer shows or queries stale DeepSeek usage when a user-selected session retains old runtime model fields; the regression assertsproviders: ["openai"]and rejectsUsage: Balance ¥42.50.src/status/status-text.tsnow uses selected usage when runtime state is stale, while preserving active-provider usage for automatic handoff provenance, existing active runtime-handoff notices, and equivalent runtime aliases./statususage through the active provider when the stored notice still matches the selected and active model refs. This is a root-cause source-of-truth boundary fix: it reuses the existing active runtime-handoff state to distinguish intentional active runtime from stale manual override state, instead of masking rendered text or adding a new compatibility fallback./statusshowsContext: 49k/123kinstead of staleContext: 49k/1.0m.node scripts/run-vitest.mjs src/auto-reply/reply/commands-status.test.ts -t "uses the session-selected model provider for /status usage"; passed.node scripts/run-vitest.mjs src/auto-reply/reply/commands-status.test.ts; covered by the requested status suite command and passed.node scripts/run-vitest.mjs src/auto-reply/reply/commands-status.test.ts src/agents/openclaw-tools.session-status.test.ts; passed.daily_fix clawsweeper-commit-reviewpassed on the submitted head.