Repository navigation
[909] CodeRabbit Review Findings — View State Isolation Bugs #929
Description
[909] CodeRabbit Review Findings — View State Isolation Bugs
PR #909 (fix(webview): isolate parallel provider view state) introduced per-view state isolation, but the CodeRabbit review identified 5 actual bugs. These are not UX enhancements — they are code correctness and test reliability issues.
Source: CodeRabbit inline comments on PR #909 (reviewed by @taltas + auto-review)
Status: PR #909 still OPEN — these should be fixed before merge.
Findings
🔴 F1: broadcastResetToAllInstances tests have no cleanup — stale providers leak into subsequent tests
File: src/core/webview/__tests__/ClineProvider.parallelMode.spec.ts (lines ~1471-1680)
CodeRabbit Comment: #3605247100
Problem: The describe("broadcastResetToAllInstances", ...) test suite creates multiple ClineProvider instances but has no afterEach to dispose them. Later tests (e.g., "single instance" at line ~1592) may run with 2+ stale providers still registered in activeInstances, making test results unreliable.
Evidence:
- Lines 1471-1680: Multiple tests create
provider1,provider2(and sometimesprovider3) vianew ClineProvider(...)and callresolveWebviewView(). - No
afterEach(() => ...dispose...)block exists in this describe. - The
_clearViewLocalStatesuite at line ~1682 does callawait provider.dispose()per test, but the broadcast tests don't.
Fix: Add afterEach to dispose all providers created in this suite:
afterEach(async () => {
for (const provider of ClineProvider.getAllInstances()) {
await provider.dispose()
}
})🔴 F2: provider.setValue() not awaited — race condition in test
File: src/core/webview/__tests__/ClineProvider.spec.ts (line ~1742)
CodeRabbit Comment: #3605247108
Problem: A test calls provider.setValue() without await, so the assertion may execute before the state mutation completes. This creates a flaky race condition where the test passes sometimes but fails under load or CI.
Evidence:
setValue()is an async method that delegates tocontextProxy.setValues()then calls_updateViewLocalStateFromMutation().- Without await, the test reads state before the mutation propagates through ContextProxy.
Fix: Add await before all provider.setValue(...) calls in tests:
// Before (flaky):
provider.setValue("mode", "architect")
expect(state.mode).toBe("architect")
// After (correct):
await provider.setValue("mode", "architect")
expect(state.mode).toBe("architect")🟡 F3: loadViewState() keys not in durable storage — extension restart causes flash
File: src/core/webview/ClineProvider.ts (lines ~436-442)
CodeRabbit Comment: #3605247125
Problem: View-specific persistence keys (__view_state_{id}_mode, etc.) are NOT included in GLOBAL_STATE_KEYS. This means ContextProxy.initialize() won't load them during extension host restart. When a tab is recreated after reload, it falls back to shared global values — causing the restored tab to flash with another tab's state before the user interacts.
Evidence:
- Lines 436-442: PERSISTENCE NOTE already documents this as a known limitation.
GLOBAL_STATE_KEYS(from@roo-code/types) only includes standard keys likemode,currentApiConfigName— NOT the view-specific variants.contextProxy.getValue(viewKey)returnsundefinedafter restart because ContextProxy's cache is rebuilt fromGLOBAL_STATE_KEYSonly.
Fix options:
- Add
__view_state_*pattern toGLOBAL_STATE_KEYS(requires type changes) - Use
vscode.workspaceStateinstead ofcontextProxy.globalStatefor view-specific keys - Accept as documented limitation (low impact — users rarely restart between tabs)
Priority: Medium. Documented, but causes visible flash on extension reload.
🟡 F4: Multiple paths modify viewLocalState directly, bypassing saveViewState()
File: src/core/webview/ClineProvider.ts (lines ~1211-1212)
CodeRabbit Comment: #3605247128
Problem: Several code paths write directly to this.viewLocalState[key] = value, bypassing the saveViewState() method which handles both the local cache update AND the persistent write to view-specific ContextProxy key. This means:
- Mode changes from history restore (line 1212) don't persist to disk
- Profile activation updates may not be persisted consistently
- If
loadViewState()runs or webview recreates, these values revert
Evidence:
- Line 1212:
this.viewLocalState.mode = historyItem.mode— direct write, nosaveViewState("mode", ...)call. - Line 1903:
this.viewLocalState.apiConfiguration = providerSettings— direct write inactivateProviderProfile(). - Other mutation paths may also bypass
saveViewState().
Fix: Centralize all view-local state mutations through a single async helper that updates both the cache and persists to disk. Replace direct writes with calls to this helper.
Priority: Medium-High. Causes state revert on webview recreation — directly impacts user experience in multi-tab mode.
🟡 F5: Profile deletion spreads getState() — persists derived fields unnecessarily
File: src/core/webview/ClineProvider.ts (lines ~1843-1849)
CodeRabbit Comment: #3605247130
Problem: The profile deletion flow calls await this.getState() which returns a merged state object containing ALL derived fields. It then spreads this into contextProxy.setValues(), persisting unrelated derived fields alongside the intended changes. This can cause:
- Fallback profile name mismatch with deleted profile's flat provider settings
- Unnecessary state writes that may conflict with other tabs' state
Evidence:
- Line 1843:
const globalSettings = await this.getState()— returns full merged state. - Lines 1845-1849:
{ ...globalSettings, currentApiConfigName: profileToActivate, listApiConfigMeta: entries }— spreads ALL ofgetState()result including derived fields likeapiConfiguration,customModePrompts, etc.
Fix: Only write the specific fields that need updating:
await this.contextProxy.setValues({
currentApiConfigName: profileToActivate,
listApiConfigMeta: entries,
})Priority: Low-Medium. Unnecessary writes but unlikely to cause visible bugs in practice.
Summary Table
| # | Finding | File | Priority | Impact |
|---|---|---|---|---|
| F1 | Test cleanup missing (broadcastResetToAllInstances) | parallelMode.spec.ts |
Low | Flaky tests |
| F2 | Missing await on setValue() | ClineProvider.spec.ts |
Medium | Race condition in tests |
| F3 | View keys not durable across restart | ClineProvider.ts:436-442 |
Medium | Flash on extension reload |
| F4 | Direct viewLocalState writes bypass saveViewState | ClineProvider.ts:1211-1212 |
Med-High | State revert on recreate |
| F5 | Spread getState() in profile deletion | ClineProvider.ts:1843-1849 |
Low-Med | Unnecessary state writes |
Notes
- PR fix(webview): isolate parallel provider view state #909 is still OPEN (not merged). These issues should be addressed before merge.
- F3 is already documented as a known limitation in the PR description.
- F1 and F2 are test-only fixes — low risk, quick wins.
- F4 is the most impactful for users — direct writes bypass persistence.
Activity
[-][909] Parallel Mode: View State Isolation Follow-ups[/-][+][909] CodeRabbit Review Findings — View State Isolation Bugs[/+]easonLiangWorldedtech commentedon Sep 4, 2026
Superseded by the vps2 tracking issue easonLiangWorldedtech#41 — the vps2 A5 re-review verified all 5 of these findings as stale or superseded; see the #41 ledger.
test(e2e): port view-state suite with reload and rehydration coverage
test(e2e): port view-state suite with reload and rehydration coverage
test(e2e): port view-state suite with reload and rehydration coverage
test(e2e): port view-state suite with reload and rehydration coverage
test(e2e): port view-state suite with reload and rehydration coverage
test(e2e): port view-state suite with reload and rehydration coverage