Skip to content

[BUG] Multi-tab mode/model settings get overwritten by latest task #908

Description

@easonLiangWorldedtech

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

  1. Open two separate Roo Code tabs (Tab A and Tab B)
  2. In Tab A, select a specific mode (e.g., "Code") and API configuration
  3. In Tab B, select a different mode (e.g., "Architect") and a different API configuration
  4. 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:

  1. When the user switches modes in any tab, handleModeSwitch() calls updateGlobalState("mode", newMode) (line 1483), updating a shared global state
  2. Similarly, activateProviderProfile() updates the global currentApiConfigName
  3. 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:

  1. loadViewState() — Loads initial state from global state into view-local buffer after dependencies are initialized
  2. saveViewState(key, value) — Updates local cache and syncs to ContextProxy; handles undefined/null clearing
  3. setupGlobalStateListener() — Listens for VSCode config changes to reload viewLocalState when other views modify global state
  4. handleModeSwitch() — Now calls saveViewState("mode", newMode) BEFORE updateGlobalState() (local-first pattern)
  5. activateProviderProfile() — Updates both viewLocalState.currentApiConfigName and viewLocalState.apiConfiguration
  6. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions