fix: prevent parent task state loss during orchestrator delegation#11281
Merged
hannesrudolph merged 2 commits intomainfrom Feb 7, 2026
Merged
fix: prevent parent task state loss during orchestrator delegation#11281hannesrudolph merged 2 commits intomainfrom
hannesrudolph merged 2 commits intomainfrom
Conversation
Contributor
Reviewed changes in a6537e6 (rebased). No new issues found. The PR-specific fixes (
Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues. |
- Snapshot arrays before passing to JsonStreamStringify to prevent lazy-read mutation bugs in saveApiConversationHistory/saveClineMessages - Return boolean success/failure from save methods instead of silently swallowing errors - Guard flushPendingToolResultsToHistory to retain in-memory state when disk save fails - Make getTaskWithId non-destructive (no longer deletes task from state when api_conversation_history.json is missing) - Add try/catch to readTaskMessages and readApiMessages for corrupted JSON graceful degradation - Add flush retry with CRITICAL logging in delegateParentAndOpenChild
- Snapshot arrays with structuredClone before JsonStreamStringify to prevent lazy-read race - Return boolean from save methods; only clear userMessageContent on success - Make getTaskWithId non-destructive (return [] instead of deleting task on missing file) - Add try/catch around JSON.parse in read paths - Add retrySaveApiConversationHistory with exponential backoff - Add comprehensive test coverage (113 tests pass) Fixes #11172
d54e340 to
a6537e6
Compare
daniel-lxs
approved these changes
Feb 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11172
Summary
Fixes the "parent task disappears in orchestrator mode" bug where parent tasks would rewind to a stale state after a child subtask returned, causing the parent to appear "lost" or "disappeared."
Root Causes
JsonStreamStringify lazy mutation bug:
saveApiConversationHistory()andsaveClineMessages()passed live array references tosafeWriteJson(), which usesJsonStreamStringifythat lazily reads elements during streaming. Elements pushed after construction were silently dropped.Silent error swallowing: Both save functions swallowed all write errors, so
flushPendingToolResultsToHistory()cleared in-memory state even when the disk write failed. The parent was then destroyed from the stack, leaving only stale disk state.Destructive
getTaskWithId(): Whenapi_conversation_history.jsonwas missing, it permanently deleted the task's HistoryItem from globalState — turning transient filesystem errors into permanent data loss.Unprotected read paths:
readTaskMessages()had no try/catch around JSON.parse;readApiMessages()caught parse errors but re-threw them.Changes
src/core/task/Task.ts: Snapshot arrays withstructuredClone()before save for deep isolation from JsonStreamStringify's lazy reads; returnbooleanfrom save methods; guardflushPendingToolResultsToHistory()to retain in-memory state on failure; addretrySaveApiConversationHistory()with exponential backoff (3 attempts: 100ms, 500ms, 1500ms)src/core/webview/ClineProvider.ts: MakegetTaskWithId()non-destructive (return empty history instead of deleting task); add try/catch aroundJSON.parseingetTaskWithId(); add flush retry with user-visible warning indelegateParentAndOpenChild()src/core/task-persistence/taskMessages.ts: Add try/catch aroundJSON.parseinreadTaskMessages()src/core/task-persistence/apiMessages.ts: Return[]instead of re-throwing inreadApiMessages()catch blocksTests