Conversation
This branch has not been deployed
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.
Mechanism
The desktop renderer kept a session-wide
interruptedboolean set by Stop (cancelRun) and cleared only by the user-submit path (submit.tsseedOptimistic). Backend-chained turns (a/goalcontinuation, a completion drain flushing queued background-process notifications) never pass through submit, so theirmessage.starthit the early-return guard ingateway-event/message-stream.tsand the whole turn was silently dropped: no bubble armed, and the latermessage.completesettled flags incompleteAssistantMessagewithout appending the persisted reply. The transcript ended abruptly whilestate.dbheld the reply.Fix: treat
message.startas the backend accepting a NEW turn, so it clears the Stop latch and arms normally instead of early-returning. One guard site changed; the sibling guards (mutateStream,finalizeInterimAssistantMessage,completeAssistantMessageinterrupted branch,session-infobusy-rearm) are untouched and still drop stale events of the CANCELLED turn while the latch is set. This mirrors the existing submit-path contract (fresh turn clears the latch) at the only boundary every backend turn crosses.RED-to-GREEN evidence
New spec
apps/desktop/src/app/session/hooks/use-message-stream/interrupted-latch.test.tsx(the issue's inlined repro, verbatim): mounts the realuseMessageStreamhook and drivesmessage.start/delta/completethrough the real handlers.state.messages(AssertionError: expected false to be trueat the chained-reply assertion).Neighbor test results
Full
use-message-streamhook suite: 48 files, 233 tests, all pass — includinginterrupted-reply.test.tsx(Stop still seals the live bubble, late deltas still dropped,status: interruptedcompletions still extend-only) andclarify-hydration,heartbeat-complete-reorder,stale-pending-settlespecs.Risks
Timing-based, not identity-proved: a redelivered
message.startfor the cancelled turn itself would now re-arm instead of being dropped (noted with aponytail:ceiling comment in code). Making this airtight needs per-turn ids on gatewaymessage.*events (the issue's option 3, a backend contract change, deliberately out of scope). A stalemessage.completeof the cancelled turn arriving AFTER a chained turn's start would settle onto the new bubble — same pre-existing ambiguity, unavoidable without event identity.Related issues
Pre-cleared as non-occupying: PR #66485 (introduced the latch), PR #99667 (hydration recovery, no latch contact), PR #120601 (supersede scoping, no latch contact), open PR #104773 (clarify-request guard only), open PR #63292 (interrupted completions as metadata), open PR #124348 (Response-stopped marker). None touches the
message.startguard.Closes #122723