Skip to content

feat(subtasks): abandon interrupted subtask — sever parent-child link and surface delegation status#935

Open
edelauna wants to merge 5 commits into
mainfrom
issue/559
Open

feat(subtasks): abandon interrupted subtask — sever parent-child link and surface delegation status#935
edelauna wants to merge 5 commits into
mainfrom
issue/559

Conversation

@edelauna

@edelauna edelauna commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Related GitHub Issue

Closes: #559

Description

This PR implements the Abandon Subtask flow end-to-end: a parent task that is waiting on a delegated subtask can sever the link if that subtask is interrupted, restoring the parent to active without losing the subtask's history.

Key changes:

  • Task lifecycle (ClineProvider, webviewMessageHandler, extension/api): abandonSubtask() atomically severs the parent-child link in the history store, transitions parent delegated → active, and closes the live child instance before the write so a later saveClineMessages can't silently reattach it. A TOCTOU guard re-checks child status inside the per-parent delegation lock.

  • evictCurrentTask(): public method that snapshots the current task, pops it via removeClineFromStack, then calls markDelegatedChildInterrupted if the child was active+delegated — so the + button and clearCurrentTask both correctly interrupt an active delegated child rather than losing the delegation state.

  • onTaskCompleted closure: explicitly transitions the task to completed in the history store before re-emitting the event, ensuring a prior interrupted status (from a cancel followed by resumption and completion) is overwritten rather than preserved by the store's merge.

  • History UI (HistoryView, TaskGroupItem, TaskItem, TaskItemFooter, SubtaskRow): delegation status badges (delegatedTag, interruptedTag).

  • i18n: all new keys present in all 18 locale files (en + 17 others).

image image
  • E2E: dedicated fixtures and markers for the abandon scenario (separate from the interrupt scenario) to avoid sequenceIndex exhaustion across sibling tests.

Test coverage additions:

  • evictCurrentTask, markDelegatedChildInterrupted (TOCTOU + error paths), onTaskCompleted (real closure via taskCreationCallback)
  • abandonSubtask: sever, ordering (close before write), TOCTOU, active-child guard, stale-parent guard
  • abandonSubtaskWithId webview message handler: happy path + error catch/log
  • API.clearCurrentTask and API.abandonSubtask passthrough
  • plusButtonClicked command handler calls evictCurrentTask

Test Procedure

  • Open a task, use new_task to delegate to a subtask, then navigate away (interrupting the subtask). In History, open the parent — it should show a "Delegated" badge and an "Abandon Subtask" button. Confirm the dialog; the parent returns to active and the subtask is no longer linked.
  • Unit: pnpm --filter zoo-code test
  • E2E: subtasks suite covers the abandon flow end-to-end

Pre-Submission Checklist

Summary by CodeRabbit

  • New Features
    • Added an API to abandon an interrupted subtask and sever its delegated parent↔child link.
    • Added “waiting on subtask” navigation in the chat header and a one-click jump to the awaiting subtask.
    • Added delegated/interrupted status badges in the task history UI.
  • Bug Fixes
    • Improved subtask eviction, delegation interruption handling, and cancellation/completion behavior to prevent stale or incorrect parent/child state changes.
  • Localization
    • Added subtask status and navigation strings across supported languages.
  • Tests
    • Expanded e2e and unit coverage for abandon/delegation/lifecycle and webview message handling.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR separates delegated-task eviction from delegation repair, preserves interrupted parent-child links, adds explicit subtask abandonment through the API and webview, updates delegated/interrupted task UI, and adds unit and end-to-end coverage.

Changes

Subtask lifecycle and abandonment

Layer / File(s) Summary
Eviction and interruption lifecycle
src/core/webview/ClineProvider.ts, src/core/webview/__tests__/*, src/activate/*, src/extension/api.ts, src/__tests__/*
removeClineFromStack performs lifecycle cleanup only; eviction marks delegated children interrupted, and completion persists completed status.
Delegation repair and redelegation
src/core/webview/ClineProvider.ts, src/__tests__/provider-delegation.spec.ts, src/__tests__/history-resume-delegation.spec.ts
Redelegation validates interrupted awaited children before severing links and preserves rollback behavior.
Explicit subtask abandonment API
packages/types/src/*, src/core/webview/*, src/extension/api.ts, src/__tests__/*
Interrupted delegated children can be abandoned through atomic history updates exposed by the extension API and webview handler.
Interrupted status UI and navigation
webview-ui/src/components/*, webview-ui/src/i18n/locales/*
Delegated tasks expose awaited-child navigation, and delegated or interrupted tasks display localized status badges.
End-to-end abandonment scenario
apps/vscode-e2e/src/fixtures/subtasks.ts, apps/vscode-e2e/src/suite/subtasks.test.ts
The e2e flow cancels and abandons a delegated child, verifies link removal, and confirms independent child completion.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

Suggested labels: awaiting-review

Suggested reviewers: navedmerchant, taltas, hannesrudolph

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes implement the interrupted state, preserve delegation on cancel, and add explicit abandon handling as required by #559.
Out of Scope Changes check ✅ Passed The added lifecycle, UI, i18n, and test changes all support the abandon/interrupted subtask objectives and appear in scope.
Title check ✅ Passed The title is concise, specific, and accurately summarizes the main abandon-subtask change.
Description check ✅ Passed The PR description includes the required issue link, change summary, test procedure, and checklist; only optional sections are largely empty.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue/559

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

@edelauna edelauna changed the title Issue/559 feat(subtasks): abandon interrupted subtask — sever parent-child link and surface delegation status Jul 18, 2026
@edelauna
edelauna marked this pull request as ready for review July 18, 2026 22:39

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (1)
webview-ui/src/i18n/locales/ca/chat.json (1)

21-22: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Translate new localization keys.

The new localization keys for subtask states were added with English fallback strings across non-English locales. Updating them to their respective translated values ensures a consistent localized user experience.

  • webview-ui/src/i18n/locales/ca/chat.json#L21-L22: Update to Catalan: "waitingOnSubtask": "Esperant la subtasca", "goToSubtask": "Anar a la subtasca".
  • webview-ui/src/i18n/locales/ca/history.json#L58-L59: Update to Catalan: "delegatedTag": "Esperant la subtasca", "interruptedTag": "Interromput".
  • webview-ui/src/i18n/locales/de/chat.json#L21-L22: Update to German: "waitingOnSubtask": "Warten auf Teilaufgabe", "goToSubtask": "Zur Teilaufgabe".
  • webview-ui/src/i18n/locales/de/history.json#L58-L59: Update to German: "delegatedTag": "Warten auf Teilaufgabe", "interruptedTag": "Unterbrochen".
  • webview-ui/src/i18n/locales/es/chat.json#L21-L22: Update to Spanish: "waitingOnSubtask": "Esperando subtarea", "goToSubtask": "Ir a la subtarea".
  • webview-ui/src/i18n/locales/es/history.json#L58-L59: Update to Spanish: "delegatedTag": "Esperando subtarea", "interruptedTag": "Interrumpido".
  • webview-ui/src/i18n/locales/fr/chat.json#L21-L22: Update to French: "waitingOnSubtask": "En attente de la sous-tâche", "goToSubtask": "Aller à la sous-tâche".
  • webview-ui/src/i18n/locales/fr/history.json#L58-L59: Update to French: "delegatedTag": "En attente de la sous-tâche", "interruptedTag": "Interrompu".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/vscode-e2e/src/fixtures/subtasks.ts`:
- Around line 388-392: Update the child fixture’s predicate to exclude requests
containing SUBTASK_ABANDON_PARENT_PROMPT, matching the interrupted scenario’s
parent-request exclusion. Keep the existing child marker, tool-call, and
follow-up answer checks unchanged so only the intended child request is
accepted.

In `@src/core/webview/__tests__/ClineProvider.taskHistory.spec.ts`:
- Around line 809-810: Replace the fixed 10 ms sleeps after TaskCompleted
emissions in the task-history tests with awaited completion of the listener
promises. Update the fake emitter used by ClineProvider task-history tests to
collect listener return promises and make emit() await them, then await emit()
directly at the affected cases around task-cb-1 and the additional TaskCompleted
scenarios.

In `@src/core/webview/ClineProvider.ts`:
- Around line 533-540: Update provider disposal to call evictCurrentTask()
instead of removeClineFromStack(), ensuring an active delegated child is marked
interrupted during shutdown while preserving the existing disposal flow.
- Around line 600-609: The in-lock transition in markDelegatedChildInterrupted
must proceed only when childHistory.status is exactly "active". Update the guard
to skip every other status, including "completed", then validate the
active-to-interrupted transition before calling
updateTaskHistory(interruptedChild).
- Around line 3599-3615: Update the delegation flow around atomicReadAndUpdate
to execute under runDelegationTransition(), removing the pre-lock
existingAwaitedChildStatus snapshot. Inside the updater, read
historyItem.awaitingChildId and its current status together, and only sever the
link when that exact child is confirmed interrupted; otherwise throw and
preserve rollback behavior.
- Around line 541-553: Ensure interruption-persistence failures are propagated
instead of swallowed: update markDelegatedChildInterrupted and its callers,
including evictCurrentTask and the corresponding paths near createTask and
abandonSubtask, to preserve rejection. Remove ineffective non-fatal catches and
prevent createTask from opening a replacement task when eviction fails, leaving
the live child/history state consistent.

In `@webview-ui/src/i18n/locales/nl/chat.json`:
- Around line 21-22: Replace the English fallback values for waitingOnSubtask
and goToSubtask with accurate Dutch translations in
webview-ui/src/i18n/locales/nl/chat.json:21-22, Polish translations in
webview-ui/src/i18n/locales/pl/chat.json:21-22, Brazilian Portuguese
translations in webview-ui/src/i18n/locales/pt-BR/chat.json:21-22, Russian
translations in webview-ui/src/i18n/locales/ru/chat.json:21-22, and Turkish
translations in webview-ui/src/i18n/locales/tr/chat.json:21-22. Also localize
the newly added status-label keys in
webview-ui/src/i18n/locales/nl/history.json:51-52,
webview-ui/src/i18n/locales/pl/history.json:51-52,
webview-ui/src/i18n/locales/pt-BR/history.json:51-52, and
webview-ui/src/i18n/locales/ru/history.json:51-52, preserving the existing keys
and JSON structure.

In `@webview-ui/src/i18n/locales/tr/history.json`:
- Around line 51-52: Replace the English placeholder values for delegatedTag and
interruptedTag with Turkish translations in
webview-ui/src/i18n/locales/tr/history.json:51-52. Add Vietnamese navigation
labels in webview-ui/src/i18n/locales/vi/chat.json:21-22 and Vietnamese status
labels in webview-ui/src/i18n/locales/vi/history.json:51-52. Add Simplified
Chinese navigation and status labels in
webview-ui/src/i18n/locales/zh-CN/chat.json:21-22 and
webview-ui/src/i18n/locales/zh-CN/history.json:51-52, and Traditional Chinese
equivalents in webview-ui/src/i18n/locales/zh-TW/chat.json:21-22 and
webview-ui/src/i18n/locales/zh-TW/history.json:51-52; preserve the existing
translation keys and JSON structure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 285a6a43-99ec-47fe-afa2-80fc1187e121

📥 Commits

Reviewing files that changed from the base of the PR and between c322f3c and c13507c.

📒 Files selected for processing (61)
  • apps/vscode-e2e/src/fixtures/subtasks.ts
  • apps/vscode-e2e/src/suite/subtasks.test.ts
  • packages/types/src/api.ts
  • packages/types/src/vscode-extension-host.ts
  • src/__tests__/abandonSubtask.spec.ts
  • src/__tests__/api-subtask.spec.ts
  • src/__tests__/helpers/provider-stub.ts
  • src/__tests__/history-resume-delegation.spec.ts
  • src/__tests__/provider-delegation.spec.ts
  • src/__tests__/removeClineFromStack-delegation.spec.ts
  • src/__tests__/single-open-invariant.spec.ts
  • src/activate/__tests__/registerCommands.spec.ts
  • src/activate/registerCommands.ts
  • src/core/webview/ClineProvider.ts
  • src/core/webview/__tests__/ClineProvider.flicker-free-cancel.spec.ts
  • src/core/webview/__tests__/ClineProvider.taskHistory.spec.ts
  • src/core/webview/__tests__/webviewMessageHandler.abandonSubtask.spec.ts
  • src/core/webview/webviewMessageHandler.ts
  • src/extension/api.ts
  • webview-ui/src/components/chat/TaskHeader.tsx
  • webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx
  • webview-ui/src/components/history/SubtaskRow.tsx
  • webview-ui/src/components/history/TaskItemFooter.tsx
  • webview-ui/src/components/history/TaskStatusBadge.tsx
  • webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx
  • webview-ui/src/i18n/locales/ca/chat.json
  • webview-ui/src/i18n/locales/ca/history.json
  • webview-ui/src/i18n/locales/de/chat.json
  • webview-ui/src/i18n/locales/de/history.json
  • webview-ui/src/i18n/locales/en/chat.json
  • webview-ui/src/i18n/locales/en/history.json
  • webview-ui/src/i18n/locales/es/chat.json
  • webview-ui/src/i18n/locales/es/history.json
  • webview-ui/src/i18n/locales/fr/chat.json
  • webview-ui/src/i18n/locales/fr/history.json
  • webview-ui/src/i18n/locales/hi/chat.json
  • webview-ui/src/i18n/locales/hi/history.json
  • webview-ui/src/i18n/locales/id/chat.json
  • webview-ui/src/i18n/locales/id/history.json
  • webview-ui/src/i18n/locales/it/chat.json
  • webview-ui/src/i18n/locales/it/history.json
  • webview-ui/src/i18n/locales/ja/chat.json
  • webview-ui/src/i18n/locales/ja/history.json
  • webview-ui/src/i18n/locales/ko/chat.json
  • webview-ui/src/i18n/locales/ko/history.json
  • webview-ui/src/i18n/locales/nl/chat.json
  • webview-ui/src/i18n/locales/nl/history.json
  • webview-ui/src/i18n/locales/pl/chat.json
  • webview-ui/src/i18n/locales/pl/history.json
  • webview-ui/src/i18n/locales/pt-BR/chat.json
  • webview-ui/src/i18n/locales/pt-BR/history.json
  • webview-ui/src/i18n/locales/ru/chat.json
  • webview-ui/src/i18n/locales/ru/history.json
  • webview-ui/src/i18n/locales/tr/chat.json
  • webview-ui/src/i18n/locales/tr/history.json
  • webview-ui/src/i18n/locales/vi/chat.json
  • webview-ui/src/i18n/locales/vi/history.json
  • webview-ui/src/i18n/locales/zh-CN/chat.json
  • webview-ui/src/i18n/locales/zh-CN/history.json
  • webview-ui/src/i18n/locales/zh-TW/chat.json
  • webview-ui/src/i18n/locales/zh-TW/history.json

Comment thread apps/vscode-e2e/src/fixtures/subtasks.ts
Comment thread src/core/webview/__tests__/ClineProvider.taskHistory.spec.ts Outdated
Comment thread src/core/webview/ClineProvider.ts
Comment thread src/core/webview/ClineProvider.ts Outdated
Comment thread src/core/webview/ClineProvider.ts Outdated
Comment thread src/core/webview/ClineProvider.ts Outdated
Comment on lines +21 to +22
"waitingOnSubtask": "Waiting on subtask",
"goToSubtask": "Go to subtask"

@coderabbitai coderabbitai Bot Jul 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize all newly added strings.

The same English fallback text was added to multiple non-English locale files, so users see untranslated navigation and status labels.

  • webview-ui/src/i18n/locales/nl/chat.json#L21-L22: provide Dutch task-navigation translations.
  • webview-ui/src/i18n/locales/nl/history.json#L51-L52: provide Dutch status-label translations.
  • webview-ui/src/i18n/locales/pl/chat.json#L21-L22: provide Polish task-navigation translations.
  • webview-ui/src/i18n/locales/pl/history.json#L51-L52: provide Polish status-label translations.
  • webview-ui/src/i18n/locales/pt-BR/chat.json#L21-L22: provide Brazilian Portuguese task-navigation translations.
  • webview-ui/src/i18n/locales/pt-BR/history.json#L51-L52: provide Brazilian Portuguese status-label translations.
  • webview-ui/src/i18n/locales/ru/chat.json#L21-L22: provide Russian task-navigation translations.
  • webview-ui/src/i18n/locales/ru/history.json#L51-L52: provide Russian status-label translations.
  • webview-ui/src/i18n/locales/tr/chat.json#L21-L22: provide Turkish task-navigation translations.
📍 Affects 9 files
  • webview-ui/src/i18n/locales/nl/chat.json#L21-L22 (this comment)
  • webview-ui/src/i18n/locales/nl/history.json#L51-L52
  • webview-ui/src/i18n/locales/pl/chat.json#L21-L22
  • webview-ui/src/i18n/locales/pl/history.json#L51-L52
  • webview-ui/src/i18n/locales/pt-BR/chat.json#L21-L22
  • webview-ui/src/i18n/locales/pt-BR/history.json#L51-L52
  • webview-ui/src/i18n/locales/ru/chat.json#L21-L22
  • webview-ui/src/i18n/locales/ru/history.json#L51-L52
  • webview-ui/src/i18n/locales/tr/chat.json#L21-L22
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webview-ui/src/i18n/locales/nl/chat.json` around lines 21 - 22, Replace the
English fallback values for waitingOnSubtask and goToSubtask with accurate Dutch
translations in webview-ui/src/i18n/locales/nl/chat.json:21-22, Polish
translations in webview-ui/src/i18n/locales/pl/chat.json:21-22, Brazilian
Portuguese translations in webview-ui/src/i18n/locales/pt-BR/chat.json:21-22,
Russian translations in webview-ui/src/i18n/locales/ru/chat.json:21-22, and
Turkish translations in webview-ui/src/i18n/locales/tr/chat.json:21-22. Also
localize the newly added status-label keys in
webview-ui/src/i18n/locales/nl/history.json:51-52,
webview-ui/src/i18n/locales/pl/history.json:51-52,
webview-ui/src/i18n/locales/pt-BR/history.json:51-52, and
webview-ui/src/i18n/locales/ru/history.json:51-52, preserving the existing keys
and JSON structure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified at c13507c: the new waitingOnSubtask/goToSubtask and delegatedTag/interruptedTag values are English in all 16 non-English locales (checked de, fr, nl directly). These dictionaries are otherwise fully translated (e.g. backToParentTask is localized in de/nl), so the placeholders read as mixed-language UI rather than an established fallback convention here. If translation is intentionally follow-up work, worth saying so in the PR description, which currently claims the keys are "present in all 18 locale files".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped: comment is from another GitHub bot.

Comment thread webview-ui/src/i18n/locales/tr/history.json Outdated
@github-actions github-actions Bot added the awaiting-review PR changes are ready and waiting for maintainer re-review label Jul 18, 2026
@zoomote

zoomote Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

7 issues outstanding. Findings verified against the code were posted as replies on the matching review threads; the one finding with no existing thread is carried below.

  • No UI path to abandon a subtask; abandonSubtaskWithId wiring is dead code. The PR body describes an "Abandon Subtask" button with a confirmation dialog in the History UI, but no component sends abandonSubtaskWithId (a TaskHeader test explicitly asserts the button was "removed: implicit sever on re-delegation"). As shipped, the only ways to sever a link are implicit re-delegation or the extension API — a user with an interrupted subtask has no visible abandon action, and the message type + handler (src/core/webview/webviewMessageHandler.ts:836, packages/types/src/vscode-extension-host.ts:463) are unreachable. Either restore a UI entry point or correct the PR description and drop the dead wiring.
  • src/core/webview/ClineProvider.ts:551markDelegatedChildInterrupted swallows persistence failures, making evictCurrentTask's .catch dead; a failed write leaves the child popped but active in history with no abandon path. (thread reply)
  • src/core/webview/ClineProvider.ts:3595 — re-delegation guard validates the pre-lock existingAwaitedChildStatus snapshot against the parent's current awaitingChildId; read the current child's status inside the updater instead. (thread reply)
  • src/core/webview/ClineProvider.ts:684dispose() loops on bare removeClineFromStack(), so an active delegated child at shutdown stays active forever; route through evictCurrentTask(). (thread reply)
  • apps/vscode-e2e/src/fixtures/subtasks.ts:388 — abandon child fixture predicate doesn't exclude SUBTASK_ABANDON_PARENT_MARKER, unlike the interrupted scenario it was modeled on. (thread reply)
  • webview-ui/src/i18n/locales/* — new waitingOnSubtask/goToSubtask/delegatedTag/interruptedTag values are English in all 16 non-English locales. (thread reply)
  • src/core/webview/__tests__/ClineProvider.taskHistory.spec.ts:809 — fixed 10 ms sleeps after emit instead of awaiting listener promises (also 825, 848). (thread reply)

Reviewed c13507c

@github-actions github-actions Bot added awaiting-review PR changes are ready and waiting for maintainer re-review and removed awaiting-review PR changes are ready and waiting for maintainer re-review labels Jul 19, 2026
…ildInterrupted, evict on disposal, fix delegateParentAndOpenChild TOCTOU, translate i18n keys
@github-actions github-actions Bot added awaiting-review PR changes are ready and waiting for maintainer re-review and removed awaiting-review PR changes are ready and waiting for maintainer re-review labels Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-review PR changes are ready and waiting for maintainer re-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(task-lifecycle): introduce explicit TaskStatus state machine with "interrupted" history state

1 participant