From 2a484a3ff10d0d6df3ee914f4d1bd868b698f34c Mon Sep 17 00:00:00 2001 From: SunnyShu Date: Wed, 24 Jun 2026 19:51:37 +0800 Subject: [PATCH] [AI] fix(sessions): set liveModelSwitchPending when switching to default with runtime-only fields (#96318) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 #96269 Co-authored-by: Claude --- src/sessions/model-overrides.test.ts | 33 ++++++++++++++++++++++++++++ src/sessions/model-overrides.ts | 8 +++++++ 2 files changed, 41 insertions(+) diff --git a/src/sessions/model-overrides.test.ts b/src/sessions/model-overrides.test.ts index 4029788f8940..faa7e503aa84 100644 --- a/src/sessions/model-overrides.test.ts +++ b/src/sessions/model-overrides.test.ts @@ -180,6 +180,39 @@ describe("applyModelOverrideToSessionEntry", () => { expect((entry.updatedAt ?? 0) > before).toBe(true); }); + it("sets liveModelSwitchPending when switching to default with runtime-only fields", () => { + const entry: SessionEntry = { + sessionId: "sess-96269", + updatedAt: Date.now() - 5_000, + modelProvider: "anthropic", + model: "claude-sonnet-4-6", + contextTokens: 200_000, + contextBudgetStatus: contextBudgetStatus({ + updatedAt: Date.now() - 5_000, + provider: "anthropic", + model: "claude-sonnet-4-6", + contextTokenBudget: 200_000, + }), + }; + + const result = applyModelOverrideToSessionEntry({ + entry, + selection: { + provider: "openai", + model: "gpt-5.4", + isDefault: true, + }, + markLiveSwitchPending: true, + }); + + expect(result.updated).toBe(true); + expect(entry.modelProvider).toBeUndefined(); + expect(entry.model).toBeUndefined(); + expect(entry.contextTokens).toBeUndefined(); + expect(entry.contextBudgetStatus).toBeUndefined(); + expect(entry.liveModelSwitchPending).toBe(true); + }); + it("marks non-default overrides with the provided source", () => { const entry: SessionEntry = { sessionId: "sess-5a", diff --git a/src/sessions/model-overrides.ts b/src/sessions/model-overrides.ts index 1da6b657232a..24f427d26e8d 100644 --- a/src/sessions/model-overrides.ts +++ b/src/sessions/model-overrides.ts @@ -93,6 +93,14 @@ export function applyModelOverrideToSessionEntry(params: { } } + // When switching back to the default model without override fields to delete + // (e.g. model comes from steering/fallback runtime fields), the isDefault + // branch at line 42 won't set selectionUpdated. Mark it here so that + // liveModelSwitchPending can still be set below when runtime is misaligned. + if (selection.isDefault && runtimePresent && !runtimeAligned) { + selectionUpdated = true; + } + // contextTokens are derived from the active session model. When the selected // model changes (or runtime model is already stale), the cached window can // pin the session to an older/smaller limit until another run refreshes it.