Dispatch queued messages on the real turn boundary - #342
Merged
TheGreatAxios merged 1 commit intoAug 7, 2026
Conversation
TheGreatAxios
force-pushed
the
cl-5563-queued-messages-never-dispatch-and-the-queue-readout-is
branch
2 times, most recently
from
August 7, 2026 05:19
d28f432 to
71fb50b
Compare
reactor.done fires once, at agent shutdown, never between turns, and
connector.reply never comes for a workflow/goal-governor cycle that
keeps self-continuing. Both are dead ends a text-only or tool-only
reply can hit, and both left session state stuck once nothing else
arrived: a queued message with nowhere to drain to, `run` stuck busy
so every later Enter queued instead of sending, and the phase ramp
stuck saying "working" forever.
inference.done is the one turn boundary every reactor cycle actually
guarantees. Settle isProcessing there too, using the same criterion
connector.reply already uses (no tool calls left outstanding, which
are already known by then since tool-call events stream in before
inference.done fires) -- reusing that path fixes `run`, the ramp, and
the queue drain together, with no new event type needed. When tool
calls are still outstanding the turn continues, so only the queued
messages waiting on that boundary are drained.
Also replace the queued-item transcript row's internal-state text
("queue +1 -> pending N", with the word "queue" duplicated on the
same row) with the queued message's own text and attachments, so the
depth reads once, in the notice row, and each item is identifiable.
Cancelling a queued message before it sends is deliberately out of
scope here; it needs its own transcript-row handling and its own
review, not a rider on this fix.
TheGreatAxios
force-pushed
the
cl-5563-queued-messages-never-dispatch-and-the-queue-readout-is
branch
from
August 7, 2026 05:35
71fb50b to
e2ae79b
Compare
TheGreatAxios
deleted the
cl-5563-queued-messages-never-dispatch-and-the-queue-readout-is
branch
August 7, 2026 06:36
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.
Summary
inference.done, the only turn boundary the reactor guarantees once per cycle, instead ofreactor.done, which fires once at agent shutdown and left queued messages stuck forever on any text-only replyisProcessingon that sameinference.done(once no tool calls are still outstanding, the same criterionconnector.replyalready uses) — a workflow/goal-governor cycle that keeps self-continuing may never emitconnector.reply, and without this bothrunand the phase ramp stayed stuck ("working" forever, every later Enter routed to the queue instead of sending) once nothing else arrivedqueue +1 → pending 1, with a duplicatedqueuelabel) with the queued message's own text and attachments, so depth is shown once (the notice row) and each item is identifiableVerification
bun run typecheck,bun run build, andbun testall pass — 4371 pass, 0 real failures. One local-only false positive:landing.test.ts's "no titlebar" assertion fails in this checkout because the worktree directory itself is namedcl-5563-queued-messages-never-dispatch-and-the-queue-readout-is, which contains the literal substring "queue" and leaks into the rendered title bar the test greps for — unrelated to this change and not reproducible from any other checkout pathinference.start→inference.text.delta→inference.done, no tool calls, noconnector.reply) — fails against the oldreactor.done-only drain, passes with the fixrunandturnPhasereturn to idle, and a fresh Enter dispatches viasendImmediateinstead of enqueuing into a queue nothing will ever drainturn-state.test.ts) confirminginference.donesettles the turn when no tool calls are outstanding, and does not settle when one still is — a real multi-cycle tool turn is never falsely marked idle mid-turnqueue 1once in the notice row with the message text and[1 image attached: ...]on the same row, no+1 →or duplicatedqueuelabelCloses CL-5563