Repository navigation
feat(task-lifecycle): introduce explicit TaskStatus state machine with "interrupted" history state #559
Description
Part of #357 (Epic 2: Task Lifecycle Fixes).
Context
Tracked under the #355 parallelizable-tasks refactor — proposed addition to Epic 1 (Foundation) or Epic 2 (Lifecycle Fixes).
Problem
HistoryItem.status is an ad-hoc state machine: valid states ("active", "delegated", "completed") are implicit string literals with no enforced transition table. Guards are scattered across AttemptCompletionTool.ts, ClineProvider#cancelTask, ClineProvider#reopenParentFromDelegation, and ClineProvider#removeClineFromStack, leading to the class of bugs fixed in #457 and #510.
A concrete open gap: when a delegated subtask is interrupted mid-execution (cancelTask fires), the code permanently severs the parent link (awaitingChildId → undefined, parent status → "active"). When the subtask resumes and calls attempt_completion, it has no way to re-attach to its parent and falls through to a standalone "Start New Task" flow. Users experience this as "subtask didn't report back to parent" — confirmed by user reports.
Root cause: cancelTask conflates two distinct operations:
- Aborting the running LLM stream (transient, always correct)
- Severing the parent–child delegation link (should be an explicit user choice, not an automatic side effect)
There is no history pseudo-state (in the HSM sense) — no persisted record that a task was interrupted from a delegated context — so the relationship cannot be restored after reload.
The cancelledDelegationChildIds in-memory Set is a partial workaround but resets on extension reload.
Proposed Solution
Phase 1 — Minimal (ship independently, low risk)
Add "interrupted" as a valid HistoryItem status:
// Before
status: "active" | "delegated" | "completed"
// After
status: "active" | "delegated" | "interrupted" | "completed"Transition changes:
cancelTaskon a delegated child → child status:"active" → "interrupted", parent retainsstatus: "delegated"andawaitingChildId(link preserved)AttemptCompletionToolalready handlesparentStatus === "delegated"— no change needed therereopenParentFromDelegationguard already accepts"delegated"— no change needed- UI: parent in
"delegated"state with child in"interrupted"shows "Subtask was interrupted — resume or abandon" rather than silently detaching
Files touched: HistoryItem type, ClineProvider#cancelTask, ClineProvider#removeClineFromStack, UI task list rendering for the interrupted state.
Phase 2 — Full state machine (part of #355 Epic 1)
Replace scattered status guards with an explicit transition table (XState or lightweight equivalent):
IDLE → ACTIVE → DELEGATED → ACTIVE (reopen) → COMPLETED
↘ INTERRUPTED → ACTIVE (resume) / COMPLETED (finish after interrupt)
ACTIVE → INTERRUPTED → ACTIVE (resume) → COMPLETED
- Transitions enforced as discriminated unions at compile time
- Single
TaskStateMachineowns all valid transitions — no more scatteredif (status === "delegated")guards - Slots into the
TaskRegistry(Story 3.2a) as the authoritative state authority per task
Acceptance Criteria
- A delegated subtask that is interrupted mid-execution can resume and successfully report back to its parent
- Parent task is not silently detached when child is interrupted — link survives extension reload
- User can explicitly break the parent–child link ("Abandon subtask") which is the only action that currently auto-fires on cancel
- All existing subtask e2e scenarios continue to pass (
apps/vscode-e2esubtasks suite) - No regression in the
cancelTask→ standalone-child path (non-delegated tasks unaffected)
Related
- Closes the "interrupted subtask shows Start New Task" user report (Reddit thread, ~June 2026)
- Depends on / coordinates with: [Tracking] Parallelizable Tasks — Implementation Roadmap #355 Epic 1 Foundation, fix(AttemptCompletionTool): child tasks return to parent when parent status is active #510
- Augment Code post-mortem on VS Code extension state bugs: 70% of their bug-fix PRs traced to the same root-cause pattern (implicit state ownership, hidden side effects)
Activity
edelauna commentedon Jun 14, 2026
Phase 1 prototyped; deferring to land Epic 1+2 foundation first
The Phase 1 implementation (issue #560, PR #613) was prototyped and reviewed. The review confirmed the behavioral design is sound —
interruptedstatus, parent staysdelegated, self-heal on rehydration — but surfaced 3 blocking concurrency bugs caused by the lack of structural enforcement (unserialized mutations, volatile fail-closed guards).This validates the Phase 2 assessment in this issue: scattered
if (status === "delegated")guards are the root cause. The prototype neededrunDelegationTransitionwrappers at 5 call sites, idempotency guards in 2 methods, and a best-effort persist in a catch block — all of which would be unnecessary with the transition guard from Story 2.3 (#366) andatomicReadAndUpdate()onTaskHistoryStore.Updated plan:
interruptedas a single story on top of the transition map — valid transitions:active → interrupted,interrupted → active,interrupted → completedissue/560)Phase 2 (full
TaskStateMachinewith compile-time enforcement) remains as designed in this issue.