fix(api): defer follow-ups until Zed thread established (no divorced thread) - #2661
Merged
Merged
Conversation
…thread) Follow-up to #2660, which was incomplete. The original reproducer (re-plan from scratch + a correction during boot) still forked two threads and lost context. #2660 guarded the interrupt-poller decision site, but a SECOND dispatch path — processAnyPendingPrompt -> sendQueuedPromptToSession — is exempt from the busy-defer for interrupt prompts and dispatched the correction with an empty acp_thread_id concurrently with the initial. Two empty-thread sends -> Zed forked two threads (initial in A, correction in B); the session bound to B, which never held the initial, so the agent answered "a previous conversation context that I don't have". Fix at the real chokepoint: in sendQueuedPromptToSession the interrupt's exemption from the busy-defer is gated on the thread being established (ZedThreadID != ""). Until thread_created lands, every prompt (interrupt included) respects the busy-defer, so only the genuine first message is ever sent empty-thread; once the thread exists the interrupt is exempt again and fires into the SAME thread. Content-verified on live Zed: initial "WidgetSync over Bluetooth" + interrupt "over WiFi" -> one thread, zero empty-thread correction sends, agent response retained the feature AND applied the correction. (The #2660 validation only checked message completion/length, not retained context — hence the miss.) See design/2026-06-19-incident-interrupt-during-boot-context-loss.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Follow-up to #2660, which was incomplete. After it merged, the original reproducer (move task to backlog → re-plan from scratch → quick correction during boot) failed identically — the agent answered the correction with "a previous conversation context that I don't have."
Why #2660 missed it
#2660 guarded the interrupt poller decision site. But a second dispatch path —
processAnyPendingPrompt → sendQueuedPromptToSession— is exempt from the busy-defer for interrupt prompts, and it dispatched the correction with an emptyacp_thread_idconcurrently with the initial (also empty-thread). Two empty-thread sends → Zed forked two threads: initial's spec work inA, correction inB. The session bound toB, which never held the initial → context loss.Also: #2660's "validation" checked message completion + length, not whether the agent retained context, so it passed while the bug was live.
Fix — at the real chokepoint
In
sendQueuedPromptToSession, the interrupt's exemption from the busy-defer is only safe once the thread exists. Gate it onthreadNotEstablished := session.Metadata.ZedThreadID == "":Until
thread_createdpopulatesZedThreadID, every prompt (interrupt included) respects the busy-defer, so only the genuine first message is ever sent empty-thread. Once the thread exists, the interrupt is exempt again and fires into the same thread.Validation (content-verified, live Zed, meta.helix.ml)
Fresh spec task, initial = "WidgetSync … over Bluetooth", interrupt during boot = "Actually … over WiFi":
04101764) — no fork.TestWebSocketSyncSuite+TestPromptHistoryHandlersSuitegreen.Incident report updated with this follow-up:
design/2026-06-19-incident-interrupt-during-boot-context-loss.md.🤖 Generated with Claude Code