From fa09321ef26c87e7910a218528826db9328f160d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E8=89=BA=E9=9F=AC=28yangyitao=29?= Date: Sun, 15 Mar 2026 02:54:29 +0000 Subject: [PATCH] fix(sessions): reject empty provider/model IDs in model override selection --- src/sessions/model-overrides.test.ts | 28 ++++++++++++++++++++++++++++ src/sessions/model-overrides.ts | 6 ++++++ 2 files changed, 34 insertions(+) diff --git a/src/sessions/model-overrides.test.ts b/src/sessions/model-overrides.test.ts index 7545cd49548..9226de3ceaa 100644 --- a/src/sessions/model-overrides.test.ts +++ b/src/sessions/model-overrides.test.ts @@ -92,6 +92,34 @@ describe("applyModelOverrideToSessionEntry", () => { expect(entry.updatedAt).toBe(before); }); + it("rejects empty provider or model IDs without mutating the entry", () => { + const before = Date.now() - 5_000; + const entry: SessionEntry = { + sessionId: "sess-empty", + updatedAt: before, + providerOverride: "anthropic", + modelOverride: "claude-sonnet-4-6", + }; + + const emptyProvider = applyModelOverrideToSessionEntry({ + entry, + selection: { provider: "", model: "gpt-5.2" }, + }); + expect(emptyProvider.updated).toBe(false); + expect(entry.providerOverride).toBe("anthropic"); + expect(entry.modelOverride).toBe("claude-sonnet-4-6"); + expect(entry.updatedAt).toBe(before); + + const emptyModel = applyModelOverrideToSessionEntry({ + entry, + selection: { provider: "openai", model: " " }, + }); + expect(emptyModel.updated).toBe(false); + expect(entry.providerOverride).toBe("anthropic"); + expect(entry.modelOverride).toBe("claude-sonnet-4-6"); + expect(entry.updatedAt).toBe(before); + }); + it("clears stale contextTokens when switching back to the default model", () => { const before = Date.now() - 5_000; const entry: SessionEntry = { diff --git a/src/sessions/model-overrides.ts b/src/sessions/model-overrides.ts index dbbc95e23b7..a4190aef959 100644 --- a/src/sessions/model-overrides.ts +++ b/src/sessions/model-overrides.ts @@ -17,6 +17,12 @@ export function applyModelOverrideToSessionEntry(params: { let updated = false; let selectionUpdated = false; + // Guard against empty/whitespace-only provider or model IDs that can result + // from truncated picker payloads or malformed directive parsing (#46700). + if (!selection.isDefault && (!selection.provider.trim() || !selection.model.trim())) { + return { updated: false }; + } + if (selection.isDefault) { if (entry.providerOverride) { delete entry.providerOverride;