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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions