Uh oh!
There was an error while loading. Please reload this page.
fix(session): restart the loop for queued input stranded by an interrupt - #40956
fix(session): restart the loop for queued input stranded by an interrupt#40956zhihanliu-collab wants to merge 1 commit into
Conversation
Interrupting a turn (Esc / POST /session/:id/abort) silently dropped queued input: user messages admitted while the turn was running stay persisted in history behind the aborted assistant message, but nothing restarts the run loop after a cancel, so they are never answered. After SessionRunState.cancel settles, look at the reconstructed history the same way the run loop does: if there is a user message behind the last assistant message (queue input that was never picked up), fork a fresh loop for it. Bare interrupts keep today's semantics — with nothing queued, the interrupted question is not re-answered. Regression tests both directions: stranded queued input gets answered after cancel; a bare cancel starts no new turn. Fixesanomalyco#40955
The following comment was made by an LLM, it may be inaccurate: Based on my search, I found one potentially related PR: PR #39189 — This PR addresses a similar issue with queued messages being lost during session interrupts, though from the TUI perspective. Since your PR (40956) is fixing the server-side The other results (feat PRs around queued messages) are feature additions rather than related bug fixes. |
Issue for this PR
Closes#40955
Type of change
What does this PR do?
Interrupting a turn (Esc, or
POST /session/:id/abort) silently drops queued input. The queue is durability-only: user messages sent while a turn runs are persisted immediately and normally get picked up by a later loop iteration (steer semantics). ButSessionPrompt.cancelonly stops the run — after the abort nothing ever restarts the loop, so any user message sitting behind the last assistant message is stranded in history forever, never answered. In the TUI this shows up as theQUEUEDtag silently vanishing.The fix sits right after the cancel settles: re-read the reconstructed history the same way
SessionPrompt.rundoes (MessageV2.filterCompactedEffect+MessageV2.latest), and if there is a user message behind the last assistant message — i.e. queued input that was never picked up — fork a fresh loop for it. Bare interrupts deliberately keep today's semantics: when nothing was queued behind the aborted turn, the interrupted in-flight question is not re-answered (second test below pins this, and by extension interrupt→interrupt still gives a full stop, since a re-esc'd turn leaves its new aborted assistant message tail-most).How did you verify your code works?
Two regression tests in
packages/opencode/test/session/prompt.test.tsagainst the test LLM server:cancel restarts the loop for queued input stranded behind the aborted turn— turn hung mid-stream, user message admitted while busy, cancel; the stranded message gets a finished assistant reply afterwards and the session returns to idle.cancel without queued input does not restart the loop— bare cancel with only the in-flight question; no new turn is started, nothing gets answered, session stays idle.Plus: full
test/session/suite (399 tests) green, fullprompt.test.ts(59) green,tsgo --noEmitclean.Screenshots / recordings
N/A (server-side behavior change).
Checklist