Skip to content

[BUG] Parent-child task delegation across parallel tabs may lose state #921

Description

@easonLiangWorldedtech

[BUG] Parent-child task delegation across parallel tabs may lose state

Problem

When a parent task is in Tab A and a child task opens in Tab B (parallel tabs), delegateParentAndOpenChild() may produce state inconsistency due to view-local state isolation. PR #909's viewLocalState buffer gives each tab its own independent mode/apiConfig, but the delegation logic's atomicReadAndUpdate pattern only considers single-instance scenarios (#357), not cross-tab scenarios.

Evidence

Code locations:

Reproduction:

1. Open Tab A (sidebar) → start Task X (parent task)
2. In Tab A, trigger delegation: parent delegates to child task Y
3. Child task Y opens in Tab B (editor tab) — uses view-local state from Tab B
4. Parent task X continues in Tab A — uses view-local state from Tab A
5. If user switches mode/apiConfig in Tab B while delegation is active:
   - Tab B's viewLocalState changes
   - Tab A still sees old viewLocalState (isolated)
   - Delegation state may become inconsistent (parent thinks child is in "code" mode, but it's actually "architect")

Current test coverage:

  • ✅ Single instance: ClineProvider.sticky-mode.spec.ts — concurrent mode switches on same task
  • ❌ Cross-instance: No test for delegation across parallel tabs with different view-local states

Root Cause

PR #909's view-local state isolation design:

// ClineProvider.ts (simplified)
class ClineProvider {
    private viewLocalState: Partial<ExtensionState> = {}
    
    getState() {
        return { ...globalState, ...this.viewLocalState } // Local overrides global
    }
}

When delegation occurs:

  1. Tab A (parent): delegateParentAndOpenChild() → reads parent's viewLocalState
  2. Creates child task with mode/apiConfig from Tab A's viewLocalState
  3. Child opens in Tab B → Tab B has its own viewLocalState (different mode/apiConfig)
  4. Parent continues in Tab A → still sees old state
  5. Inconsistency: parent thinks child is using "code" mode, but actually child in Tab B is using "architect"

This issue is similar to the delegateParentAndOpenChild serialization in #357 [Epic 2] Task Lifecycle Fixes, but #357 only handles single-instance concurrency.

Proposed Fix

Option A: Pass explicit state snapshot during delegation (recommended)

// In ClineProvider.ts — capture state at delegation time, not read from viewLocalState
async delegateParentAndOpenChild(parentTaskId: string): Promise<void> {
    // Capture current state explicitly (don't rely on viewLocalState which may change)
    const snapshot = {
        mode: this.viewLocalState.mode ?? this.globalState.mode,
        apiConfigName: this.viewLocalState.currentApiConfigName ?? this.globalState.currentApiConfigName,
        // ... other fields needed for child task
    }
    
    await atomicReadAndUpdate(async () => {
        const parent = await this.getTask(parentTaskId)
        const child = await this.createChildTask(parent, snapshot)
        // Child uses explicit snapshot, not viewLocalState
    })
}

Option B: Cross-tab state sync on delegation

// When child opens in Tab B, sync parent's state to Tab B's viewLocalState
async delegateParentAndOpenChild(parentTaskId: string): Promise<void> {
    const parentSnapshot = this.captureState()
    
    await atomicReadAndUpdate(async () => {
        // ... create child task
        
        // Notify Tab B (via VSCode window events or postMessage) to sync state
        vscode.postMessage({ 
            type: 'syncDelegationState', 
            snapshot: parentSnapshot 
        })
    })
}

// In ExtensionStateContext.tsx — handle syncDelegationState
case 'syncDelegationState': {
    setViewLocalState(prev => ({ ...prev, ...payload.snapshot }))
    break
}

Option C: Add "delegated" flag to viewLocalState

  • When a task is delegated, mark viewLocalState.isDelegated = true
  • Delegated tasks use global state (no view-local override)
  • Simple but limiting: delegated tasks cannot have their own per-view mode

Scope

  • Impact: Users who delegate tasks across parallel tabs — delegation may lose context or use wrong mode/apiConfig
  • Risk: Low-Medium — Option A is safest (explicit snapshot), Option B requires cross-tab communication
  • Priority: Medium-High (delegation is core feature, state loss causes confusion)

Related Issues

Testing Requirements

  1. Add test in ClineProvider.parallelMode.spec.ts:

    it("delegates parent task from Tab A to child task in Tab B with correct state", async () => {
        // Create 2 ClineProvider instances (Tab A, Tab B)
        // Tab A: start parent task, set mode = "architect"
        // Tab A: delegate to child task
        // Child opens in Tab B (with Tab B's viewLocalState)
        // Verify child uses parent's state snapshot, not Tab B's viewLocalState
    })
    
    it("handles concurrent mode switch during delegation across tabs", async () => {
        // Tab A: start delegation
        // Tab B: switch mode/apiConfig simultaneously
        // Verify delegation completes with correct state (no corruption)
    })
  2. Test delegation with different viewLocalState modes between parent/child tabs

  3. Test concurrent delegateParentAndOpenChild + handleModeSwitch across tabs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions