fix(session): answer prompts that land while the previous turn is finishing - #207
Merged
Conversation
…ishing A prompt that arrived after the running turn's last look at history but before the runner flipped the session to idle was persisted and never answered: `ensureRunning` in the `Running` state discarded the incoming work and only awaited the current run, so the session went idle with an unanswered trailing user message until the next prompt. The runner now records work that arrives during a run as a pending handle and, when the current run finishes, starts one more run instead of transitioning to idle. Callers that arrived during the run resolve when that follow-up run completes. A cancel leaves `Running` before the interrupted fiber exits, so it never restarts anything, and it fails the pending handle so those callers resolve through `onInterrupt` too. Claude-Session: https://claude.ai/code/session_01KAcoL6wpgVEs2ebUrUGpHS
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.
Problem
Runner.ensureRunningin theRunningstate discarded the incoming work and only awaited the current run.SessionPrompt.promptwrites the user message straight into history and then callsloop(), so a prompt that landed after the loop's final history read but beforefinishRunflipped the state toIdle(the window includeslastAssistant(),compaction.discard, and theTurn.Endedhooks/events) was persisted but never answered. The session went idle with an unanswered trailing user message until the next prompt.Fix
Runner-level, per CONTEXT.md ("the runner promotes one queued prompt when the Session would otherwise become idle, then reevaluates continuation before promoting another"):
Runninggains an optionalpendinghandle.ensureRunningwhile running records the arrival (latest work thunk, one shareddonedeferred) and returns an await on that deferred.finishRunstarts the pending run instead of going idle when the finishing run was not interrupted.onIdleis not fired between the two runs, so status does not flicker.Runningbefore the interrupted fiber exits, so it never restarts; it now also fails the pending deferred so callers that arrived during the run resolve throughonInterrupt. A run interrupted any other way (scope teardown) drops the pending work rather than starting work in a dying scope.Test
test/session/prompt.test.ts: "answers a prompt that lands while the previous turn is finishing" holds the first turn open in aTurn.Endedhook, sends a second prompt in that window, and asserts it gets its own reply, the history ends with an assistant message answering the last user message, the session is idle, and the fake provider has no unconsumed reply. Fails before the fix (second prompt resolved with the first turn's reply), passes after.test/session/prompt.test.ts: "cancel while a turn is finishing does not start another run" cancels inside the same window and asserts exactly oneTurn.Ended, the second reply is never requested, and the session is idle.test/effect/runner.test.ts: three new cases (follow-up run before idle, callers sharing one follow-up run, cancel dropping pending work) fail before and pass after; two existing cases that encoded the old "silently join" semantics are updated to the new contract.test/sessiondirectory: 514 pass, 0 fail.bun run typecheckclean.https://claude.ai/code/session_01KAcoL6wpgVEs2ebUrUGpHS
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.