Type
Bug (Fixed)
Problem / Value
In multi-tab mode, when users switch between tabs with different mode and API configuration selections, the active tabs settings get overwritten by the global state from another tab. This causes confusion as each tab should maintain its own independent mode/model configuration.
Context
Affects users who work with multiple Roo Code tabs simultaneously, each configured for a different mode (e.g., one tab in "Code" mode, another in "Architect" mode) or using different API provider profiles. When switching between tabs, the previously saved settings are lost and replaced by whatever was last set in any tab.
Reproduction
- Open two separate Roo Code tabs (Tab A and Tab B)
- In Tab A, select a specific mode (e.g., "Code") and API configuration
- In Tab B, select a different mode (e.g., "Architect") and a different API configuration
- Switch back to Tab A — observe that its mode/model has been overwritten by Tab Bs settings
Root Cause Analysis
The issue stems from how handleModeSwitch() updates global state without considering per-tab isolation:
- When the user switches modes in any tab,
handleModeSwitch() calls updateGlobalState("mode", newMode) (line 1483), updating a shared global state
- Similarly,
activateProviderProfile() updates the global currentApiConfigName
- When
showTaskWithId() restores a task via createTaskWithHistoryItem(), it reads from this global state (line 1041) rather than properly isolating per-tab settings
Since all tabs share the same ClineProvider instances global state, any mode switch in one tab affects what other tabs see when theyre restored.
Actual Fix (PR #XXX)
Implemented view-local state isolation — a more comprehensive solution than the suggested fix:
Architecture Change
- Added
viewId property to each ClineProvider instance for unique identification
- Implemented
viewLocalState buffer (Partial<ExtensionState>) per provider instance
- Modified
getState() to merge viewLocalState on top of global state with proper precedence
Key Changes in ClineProvider.ts:
loadViewState() — Loads initial state from global state into view-local buffer after dependencies are initialized
saveViewState(key, value) — Updates local cache and syncs to ContextProxy; handles undefined/null clearing
setupGlobalStateListener() — Listens for VSCode config changes to reload viewLocalState when other views modify global state
handleModeSwitch() — Now calls saveViewState("mode", newMode) BEFORE updateGlobalState() (local-first pattern)
activateProviderProfile() — Updates both viewLocalState.currentApiConfigName and viewLocalState.apiConfiguration
createTaskWithHistoryItem() — Preserves restored task mode in both global state AND viewLocalState
Merge Precedence in getState():
const mergedStateValues = { ...stateValues, ...this.viewLocalState }
// apiConfiguration: { ...providerSettings, ...mergedStateValues.apiConfiguration }
// All other fields use mergedStateValues for local override support
Testing
- 22 new unit tests in
ClineProvider.parallelMode.spec.ts covering:
- viewId uniqueness across instances
- mode/apiConfig isolation between parallel tabs
- saveViewState/loadViewState behavior including undefined clearing
- getState merge precedence (local overrides global)
- GlobalState listener handling
- handleModeSwitch/activateProviderProfile integration
- Multi-instance isolation (3+ concurrent providers)
- All 2179 existing core tests pass (no regression)
Constraints Met
- ✅ Backward compatible — existing task history items without
mode/apiConfigName work correctly
- ✅ Works with
lockApiConfigAcrossModes enabled
- ✅ No breaking changes to public API or existing behavior for single-tab usage
Type
Bug (Fixed)
Problem / Value
In multi-tab mode, when users switch between tabs with different mode and API configuration selections, the active tabs settings get overwritten by the global state from another tab. This causes confusion as each tab should maintain its own independent mode/model configuration.
Context
Affects users who work with multiple Roo Code tabs simultaneously, each configured for a different mode (e.g., one tab in "Code" mode, another in "Architect" mode) or using different API provider profiles. When switching between tabs, the previously saved settings are lost and replaced by whatever was last set in any tab.
Reproduction
Root Cause Analysis
The issue stems from how
handleModeSwitch()updates global state without considering per-tab isolation:handleModeSwitch()callsupdateGlobalState("mode", newMode)(line 1483), updating a shared global stateactivateProviderProfile()updates the globalcurrentApiConfigNameshowTaskWithId()restores a task viacreateTaskWithHistoryItem(), it reads from this global state (line 1041) rather than properly isolating per-tab settingsSince all tabs share the same ClineProvider instances global state, any mode switch in one tab affects what other tabs see when theyre restored.
Actual Fix (PR #XXX)
Implemented view-local state isolation — a more comprehensive solution than the suggested fix:
Architecture Change
viewIdproperty to each ClineProvider instance for unique identificationviewLocalStatebuffer (Partial<ExtensionState>) per provider instancegetState()to mergeviewLocalStateon top of global state with proper precedenceKey Changes in
ClineProvider.ts:loadViewState()— Loads initial state from global state into view-local buffer after dependencies are initializedsaveViewState(key, value)— Updates local cache and syncs to ContextProxy; handles undefined/null clearingsetupGlobalStateListener()— Listens for VSCode config changes to reload viewLocalState when other views modify global statehandleModeSwitch()— Now callssaveViewState("mode", newMode)BEFOREupdateGlobalState()(local-first pattern)activateProviderProfile()— Updates bothviewLocalState.currentApiConfigNameandviewLocalState.apiConfigurationcreateTaskWithHistoryItem()— Preserves restored task mode in both global state AND viewLocalStateMerge Precedence in
getState():Testing
ClineProvider.parallelMode.spec.tscovering:Constraints Met
mode/apiConfigNamework correctlylockApiConfigAcrossModesenabled