Skip to content

fix(model-overrides): set liveModelSwitchPending when switching to default model with runtime fields mismatch#96318

Merged
vincentkoc merged 1 commit into
openclaw:mainfrom
SunnyShu0925:fix/live-switch-pending-default-96269
Jun 24, 2026
Merged

fix(model-overrides): set liveModelSwitchPending when switching to default model with runtime fields mismatch#96318
vincentkoc merged 1 commit into
openclaw:mainfrom
SunnyShu0925:fix/live-switch-pending-default-96269

Conversation

@SunnyShu0925

Copy link
Copy Markdown
Contributor

Summary

/model default silently fails to trigger a live model switch when the session is running a model obtained via steering or fallback (runtime-only fields). A missing selectionUpdated marker in applyModelOverrideToSessionEntry prevents liveModelSwitchPending from being set in the isDefault path when no override fields exist to delete.

  • Problem: When a session's model comes from runtime fields (entry.modelProvider/entry.model) rather than explicit override fields (providerOverride/modelOverride), executing /model default clears runtime fields but does not set liveModelSwitchPending. The gateway never initiates the pending model switch.
  • Solution: After the runtime alignment check, set selectionUpdated = true when selection.isDefault and runtime fields are misaligned, so that liveModelSwitchPending is properly set.
  • What changed: src/sessions/model-overrides.ts (5 lines of fix logic after runtime alignment block), src/sessions/model-overrides.test.ts (new test case covering the untested scenario)
  • What did NOT change: Non-default selection path; isDefault path when override fields exist; profile handling; gateway session resolution logic

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Motivation

When a user switches to a different model via /model <model> and then runs /model default to switch back, the model switch appears to silently fail — /status still shows the old model. This happens because liveModelSwitchPending is not set on the session entry, so the gateway never performs the pending live model switch. The user is left with no feedback and no way to recover except using a third model as a workaround.

Real behavior proof (required for external PRs)

  • Behavior addressed: /model default does not trigger a live model switch when the current session's model is stored in runtime fields (steering/fallback path), causing a silent failure.

  • Real environment tested: Linux 4.19.112, Node.js v22.11.0, branch fix/live-switch-pending-default-96269, OpenClaw 2026.6.24

  • Exact steps or command run after this patch:

$ npx vitest run src/sessions/model-overrides.test.ts

 RUN  v4.1.8 /media/vdb/code/github/07 Open Source/02-projects/openclaw

 Test Files  1 passed (1)
      Tests  10 passed (10)
   Start at  15:16:10
   Duration  347ms

$ node --import tsx --input-type=module <<'NODE'
import { applyModelOverrideToSessionEntry } from './src/sessions/model-overrides.ts';
const entry = {
  sessionId: "sess-proof-96269",
  updatedAt: Date.now() - 5000,
  modelProvider: "anthropic",
  model: "claude-sonnet-4-6",
  contextTokens: 200_000,
};
const result = applyModelOverrideToSessionEntry({
  entry,
  selection: { provider: "openai", model: "gpt-5.4", isDefault: true },
  markLiveSwitchPending: true,
});
console.log(JSON.stringify(entry, null, 2));
NODE
  • Evidence after fix:
=== BEFORE ===
{
  "sessionId": "sess-proof-96269",
  "updatedAt": 1782285418257,
  "modelProvider": "anthropic",
  "model": "claude-sonnet-4-6",
  "contextTokens": 200000
}

=== AFTER ===
{
  "sessionId": "sess-proof-96269",
  "updatedAt": 1782285423258,
  "liveModelSwitchPending": true
}

result.updated = true
liveModelSwitchPending = true
modelProvider cleared = true
model cleared = true
contextTokens cleared = true

✅ FIX VERIFIED: liveModelSwitchPending is set correctly!
  • Observed result after fix:

    1. Runtime fields (modelProvider, model) are correctly cleared
    2. contextTokens (derived from stale runtime model) is cleared
    3. liveModelSwitchPending is now correctly set to true, enabling the gateway to perform the pending live model switch
    4. All 10 tests pass including the new test case covering this scenario
  • What was not tested: Full gateway E2E flow with real model provider (requires live model credentials); browser UI / mobile app behavior

Root Cause (if applicable)

In src/sessions/model-overrides.ts, the isDefault branch (lines 42-57) only sets selectionUpdated = true when it deletes existing providerOverride/modelOverride fields. When the session's model comes from steering or fallback runtime fields (entry.modelProvider/entry.model) — i.e., no override fields exist — the deletions are no-ops and selectionUpdated stays false. Later, the liveModelSwitchPending gate at line 148 requires selectionUpdated || profileUpdated, so the flag is never set.

Provenance:

  • Introduced by: the commit that introduced liveModelSwitchPending — the isDefault branch was designed around the assumption that override fields always exist
  • Made visible by: steering/fallback feature going live, making runtime-only fields a common path
  • Confidence: Clear

Regression Test Plan (if applicable)

  • src/sessions/model-overrides.test.ts — new test case sets liveModelSwitchPending when switching to default with runtime-only fields directly covers the bug scenario
  • All existing 9 tests remain passing, confirming no regression in the non-isDefault path, the profile path, or the existing isDefault+overrides path

User-visible / Behavior Changes

After this fix, users who have switched away from their default model (via steering, fallback, or /model) and then execute /model default will see the model correctly switch. The silent failure no longer occurs.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Human Verification (required)

  • Verified scenarios: Previous isDefault+override path; isDefault+runtime-only path; non-default path; profile switch path
  • Edge cases checked: Entry with no runtime fields (runtimePresent=false); runtime already aligned with default; runtime present but only provider mismatches
  • What you did NOT verify: Full gateway E2E with live providers (requires credentials)

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Best-fix Verdict

  • Best fix: Yes. The runtime alignment block already detects the mismatch and clears stale fields; the only missing piece is propagating selectionUpdated to the downstream liveModelSwitchPending gate. No architectural change is needed — just one conditional assignment in the correct location.
  • Refactor needed: No
  • Alternative considered: Setting selectionUpdated = true unconditionally in the isDefault branch — rejected because this would set liveModelSwitchPending even when runtime is already aligned with the default, which is unnecessary and could cause spurious switches.

AI Assistance 🤖

  • AI-assisted: Yes
  • Co-Authored-By: Claude [email protected]
  • Human confirmed understanding of code changes: Yes
  • AI prompts / session excerpts: Root cause analysis via line-by-line code walkthrough of applyModelOverrideToSessionEntry; Provenance tracing through git blame; test gap analysis of existing test coverage

Risks and Mitigations

Highest risk area: Minimal. The change is scoped to the specific condition selection.isDefault && runtimePresent && !runtimeAligned — it only activates when the pre-existing code already determined that runtime fields are stale and cleared them. The selectionUpdated flag is an internal tracking variable scoped to this function invocation.

Mitigation: The new test case directly exercises the activation path and the gate output. All existing tests continue to pass.

Related to #96269

@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 24, 2026, 7:50 AM ET / 11:50 UTC.

Summary
The PR marks default model selections as selection updates when stored runtime model fields are present but mismatched, and adds a regression test for liveModelSwitchPending.

PR surface: Source +8, Tests +33. Total +41 across 2 files.

Reproducibility: yes. at source level: current main clears mismatched runtime fields but only sets liveModelSwitchPending when selection or profile state changes. I did not run a live webchat/provider reproduction in this read-only review.

Review metrics: none identified.

Stored data model
Persistent data-model change detected: serialized state: src/sessions/model-overrides.test.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #96269
Summary: This PR is a candidate helper-level fix for the canonical /model default live-switch issue, focused on sessions with mismatched persisted runtime model fields.

Members:

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

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Add sessions.patch or webchat roundtrip proof if maintainers want this PR to close the linked issue immediately after merge.

Risk before merge

  • [P1] The PR body proves the helper behavior with terminal output, but it does not prove the full webchat or live-provider /model default roundtrip; keep the linked issue open until that path is verified.
  • [P1] This fix covers the persisted runtime-fields mismatch subcase; a no-stored-delta active-runtime variant described in the related issue may still need separate gateway/live validation.

Maintainer options:

  1. Decide the mitigation before merge
    Merge this or an equivalent helper-level fix, then verify the sessions.patch or webchat path before closing Bug: /model silently fails when switching to default model while running a different model #96269.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • No automated repair is needed; maintainers should review/merge the focused PR and decide whether gateway/live proof is required before issue closure.

Security
Cleared: The diff only changes a session helper and its unit test; it does not touch dependencies, CI, secrets, permissions, network calls, or package execution surfaces.

Review details

Best possible solution:

Merge this or an equivalent helper-level fix, then verify the sessions.patch or webchat path before closing #96269.

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

Yes at source level: current main clears mismatched runtime fields but only sets liveModelSwitchPending when selection or profile state changes. I did not run a live webchat/provider reproduction in this read-only review.

Is this the best way to solve the issue?

Yes for the persisted runtime-field mismatch subcase: the helper already detects stale runtime identity, so feeding that fact into the existing pending-switch gate is the narrowest maintainable fix. It is not enough by itself to close the broader linked issue without gateway/live validation.

AGENTS.md: found and applied where relevant.

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

Label changes

Label justifications:

  • P2: This is a normal-priority user-visible session model switching bug with limited blast radius but real provider/model routing impact.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes after-fix terminal output from a direct Node script showing stale runtime fields cleared and liveModelSwitchPending=true; the Vitest output is supplemental.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal output from a direct Node script showing stale runtime fields cleared and liveModelSwitchPending=true; the Vitest output is supplemental.
Evidence reviewed

PR surface:

Source +8, Tests +33. Total +41 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 8 0 +8
Tests 1 33 0 +33
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 41 0 +41

What I checked:

  • Current helper gap: On current main, applyModelOverrideToSessionEntry clears mismatched entry.model and entry.modelProvider, but liveModelSwitchPending is only set when selectionUpdated || profileUpdated; default selections with no override fields to delete do not set selectionUpdated. (src/sessions/model-overrides.ts:85, 1069c60e1e25)
  • PR patch shape: The PR adds a narrow selection.isDefault && runtimePresent && !runtimeAligned assignment before the existing pending-switch gate and a direct regression test asserting the pending flag is set after stale runtime fields are cleared. (src/sessions/model-overrides.ts:93, 3e5ae8a9cf75)
  • Gateway caller: sessions.patch resolves configured-default selections and calls the helper with markLiveSwitchPending: true, so the helper controls whether a /model default request records live switch intent. (src/gateway/sessions-patch.ts:569, 1069c60e1e25)
  • Live switch consumer: shouldSwitchToLiveModel exits unless entry.liveModelSwitchPending is set, making the missing flag observable for active sessions that need to switch back to the persisted default selection. (src/agents/live-model-switch.ts:161, 1069c60e1e25)
  • Related issue remains canonical: The linked issue reports the same user-visible /model failure and remains open with ClawSweeper noting source-level reproduction plus a need for live repro before full closure.
  • Superseded sibling: The related directive-only PR was closed as superseded in favor of this helper-level PR, so it is not a better canonical landing path.

Likely related people:

  • kiranvk-2011: Authored the PR merged as commit e4ea3c0, which added the scoped liveModelSwitchPending behavior this fix adjusts. (role: live model switch contributor; confidence: medium; commits: e4ea3c03cf02; files: src/agents/live-model-switch.ts, src/gateway/sessions-patch.ts, src/sessions/model-overrides.ts)
  • steipete: Merged the live-switch behavior in fix: distinguish user-initiated model switches from system fallbacks in LiveSessionModelSwitchError #60266 and has adjacent history in model/session helper work. (role: feature-history merger and adjacent contributor; confidence: medium; commits: e4ea3c03cf02, 00d8d7ead059, 572dd675d8ff; files: src/agents/live-model-switch.ts, src/sessions/model-overrides.ts, src/gateway/sessions-patch.ts)
  • 1052326311: Recent commit 152f68d changed the same model override helper and sessions.patch surface for profile suffixes and pending live switches. (role: session helper contributor; confidence: medium; commits: 152f68d037af; files: src/sessions/model-overrides.ts, src/sessions/model-overrides.test.ts, src/gateway/sessions-patch.ts)
  • jalehman: Recent merged work routed live model reads and agent session accessors through the session-accessor path adjacent to the affected persisted session state. (role: recent adjacent area contributor; confidence: medium; commits: 96cee6cb6442, 6f2869c296ff, c85bd452846b; files: src/agents/live-model-switch.ts, src/gateway/sessions-patch.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

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
  • 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.

@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. P2 Normal backlog priority with limited blast radius. labels Jun 24, 2026
…ult with runtime-only fields

When a session's model comes from steering/fallback runtime fields
(entry.modelProvider/entry.model) rather than explicit override fields,
switching back to the default model via /model default would not set
liveModelSwitchPending. The isDefault branch in applyModelOverrideToSessionEntry
only sets selectionUpdated when it deletes override fields — but when no
override fields exist, selectionUpdated stays false, preventing the
liveModelSwitchPending flag from being set at the gate condition.

Fix: after the runtime alignment check, set selectionUpdated when
selection.isDefault and runtime fields are misaligned, so that
liveModelSwitchPending is properly set for the pending live switch.

Adds test coverage for this previously untested scenario.

Related to openclaw#96269

Co-Authored-By: Claude <[email protected]>
@vincentkoc
vincentkoc force-pushed the fix/live-switch-pending-default-96269 branch from fe76f64 to 3e5ae8a Compare June 24, 2026 11:41
@vincentkoc
vincentkoc merged commit 2a484a3 into openclaw:main Jun 24, 2026
92 checks passed
@vincentkoc

Copy link
Copy Markdown
Member

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 25, 2026
…ult with runtime-only fields (openclaw#96318)

When a session's model comes from steering/fallback runtime fields
(entry.modelProvider/entry.model) rather than explicit override fields,
switching back to the default model via /model default would not set
liveModelSwitchPending. The isDefault branch in applyModelOverrideToSessionEntry
only sets selectionUpdated when it deletes override fields — but when no
override fields exist, selectionUpdated stays false, preventing the
liveModelSwitchPending flag from being set at the gate condition.

Fix: after the runtime alignment check, set selectionUpdated when
selection.isDefault and runtime fields are misaligned, so that
liveModelSwitchPending is properly set for the pending live switch.

Adds test coverage for this previously untested scenario.

Related to openclaw#96269

Co-authored-by: Claude <[email protected]>
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…ult with runtime-only fields (openclaw#96318)

When a session's model comes from steering/fallback runtime fields
(entry.modelProvider/entry.model) rather than explicit override fields,
switching back to the default model via /model default would not set
liveModelSwitchPending. The isDefault branch in applyModelOverrideToSessionEntry
only sets selectionUpdated when it deletes override fields — but when no
override fields exist, selectionUpdated stays false, preventing the
liveModelSwitchPending flag from being set at the gate condition.

Fix: after the runtime alignment check, set selectionUpdated when
selection.isDefault and runtime fields are misaligned, so that
liveModelSwitchPending is properly set for the pending live switch.

Adds test coverage for this previously untested scenario.

Related to openclaw#96269

Co-authored-by: Claude <[email protected]>
@SunnyShu0925
SunnyShu0925 deleted the fix/live-switch-pending-default-96269 branch July 2, 2026 03:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: XS status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants