From 6b186b447ca667a2c9316db8e923c2a1b657373b Mon Sep 17 00:00:00 2001 From: Roo Code Date: Thu, 30 Apr 2026 14:01:35 +0000 Subject: [PATCH] fix: preserve selected model during AI-initiated mode switches When the AI agent uses the switch_mode tool, the user's selected model should not change. Previously, handleModeSwitch would load mode-specific API configurations even for AI-initiated switches, unexpectedly changing the model mid-task. This adds a preserveApiConfig option to handleModeSwitch and passes it from SwitchModeTool so the current API config is preserved. User-initiated mode switches from the UI continue to load per-mode configs as before. Closes #12237 --- src/core/tools/SwitchModeTool.ts | 5 +++-- src/core/webview/ClineProvider.ts | 8 ++++--- .../ClineProvider.lockApiConfig.spec.ts | 22 +++++++++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/core/tools/SwitchModeTool.ts b/src/core/tools/SwitchModeTool.ts index a60ce63bded..bc35c8291f9 100644 --- a/src/core/tools/SwitchModeTool.ts +++ b/src/core/tools/SwitchModeTool.ts @@ -55,8 +55,9 @@ export class SwitchModeTool extends BaseTool<"switch_mode"> { return } - // Switch the mode using shared handler - await task.providerRef.deref()?.handleModeSwitch(mode_slug) + // Switch the mode using shared handler, preserving the current API config + // so the user's selected model doesn't change during AI-initiated switches. + await task.providerRef.deref()?.handleModeSwitch(mode_slug, { preserveApiConfig: true }) pushToolResult( `Successfully switched from ${getModeBySlug(currentMode)?.name ?? currentMode} mode to ${ diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 1106d340050..645ef4935d2 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1391,7 +1391,7 @@ export class ClineProvider * Handle switching to a new mode, including updating the associated API configuration * @param newMode The mode to switch to */ - public async handleModeSwitch(newMode: Mode) { + public async handleModeSwitch(newMode: Mode, options?: { preserveApiConfig?: boolean }) { const task = this.getCurrentTask() if (task) { @@ -1426,9 +1426,11 @@ export class ClineProvider this.emit(RooCodeEventName.ModeChanged, newMode) - // If workspace lock is on, keep the current API config — don't load mode-specific config + // If workspace lock is on, or the caller explicitly requested preserving the current + // API config (e.g. AI-initiated mode switches via the switch_mode tool), keep the + // current API config — don't load mode-specific config. const lockApiConfigAcrossModes = this.context.workspaceState.get("lockApiConfigAcrossModes", false) - if (lockApiConfigAcrossModes) { + if (lockApiConfigAcrossModes || options?.preserveApiConfig) { await this.postStateToWebview() return } diff --git a/src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts b/src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts index 2cf9d4cae8b..cc47ffa9858 100644 --- a/src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts @@ -342,6 +342,28 @@ describe("ClineProvider - Lock API Config Across Modes", () => { expect(activateProviderProfileSpy).not.toHaveBeenCalled() }) + it("skips mode-specific config lookup/load when preserveApiConfig option is true", async () => { + await mockContext.workspaceState.update("lockApiConfigAcrossModes", false) + + const getModeConfigIdSpy = vi + .spyOn(provider.providerSettingsManager, "getModeConfigId") + .mockResolvedValue("architect-profile-id") + const listConfigSpy = vi + .spyOn(provider.providerSettingsManager, "listConfig") + .mockResolvedValue([ + { name: "architect-profile", id: "architect-profile-id", apiProvider: "anthropic" }, + ]) + const activateProviderProfileSpy = vi + .spyOn(provider, "activateProviderProfile") + .mockResolvedValue(undefined) + + await provider.handleModeSwitch("architect", { preserveApiConfig: true }) + + expect(getModeConfigIdSpy).not.toHaveBeenCalled() + expect(listConfigSpy).not.toHaveBeenCalled() + expect(activateProviderProfileSpy).not.toHaveBeenCalled() + }) + it("keeps normal mode-specific lookup/load behavior when lockApiConfigAcrossModes is false", async () => { await mockContext.workspaceState.update("lockApiConfigAcrossModes", false)