Repository navigation
fix(think): submission and turn lifecycle edge cases - #2386
Conversation
- waitForSubmission throws in-turn / from onSubmissionStatus instead of deadlocking - cancelled running submissions emit terminal status once; controller registered before running hook - reset-skipped submissions visible to waiters only after their hook - activeChannel gated to the admitted turn - _lastTurnChannel set at inference start, cleared on reset - autoContinue:false mid-stream reject flushes deferred drop at finalize - dropGenerationAfterToolCall keeps later steps' text - regression tests, including ai-chat #2352 port and #2353 gaps Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: 255089f The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
✅ agents import sizes: no significant changes ( |
| const active = this._activeAdmittedTurn(); | ||
| if (active) this._lastTurnChannel = { channel: active.channel }; |
There was a problem hiding this comment.
🟡 Turn reset resurrects a cancelled channel
When resetTurnState() runs during asynchronous preparation, _runInferenceLoop restores the cancelled turn's channel afterward. Automatic continuations can then route to that channel instead of the post-reset conversation.
Learn more
A turn may pause while assembling context or awaiting beforeTurn. Meanwhile, resetTurnState() advances the turn queue's generation and clears _lastTurnChannel there. The in-flight turn is not skipped by TurnQueue; it can finish preparation after the reset. Since active was captured before that pause, recording its channel reverses the reset, and _channelForAutoContinuation uses the stale value on a later continuation.
Example: A voice turn waits in beforeTurn. A chat-clear resets the agent, then the hook finishes. The old voice turn records voice again; a later automatic continuation can inherit voice instead of resolving from the cleared or newly started conversation.
Recommended fix: Check the turn queue generation and active request again after preparation before updating _lastTurnChannel. Keep it cleared if the turn was invalidated by a reset, including when an asynchronous preparation finishes after its abort signal fires.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Valid — fixed in 255089f. _lastTurnChannel is now recorded only after _prepareInferenceInvocation succeeds and the stream starts, so a turn whose beforeTurn throws keeps the previous channel. Regression test in ws-web-channel.test.ts.
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
Record _lastTurnChannel only after inference preparation (beforeTurn) succeeds and the stream starts, so a channel-less continuation does not route to a turn that never ran. Co-authored-by: Cursor <cursoragent@cursor.com>
Follow-ups from a review of recently landed Think submission/turn PRs (#2353, #2352, #2351, #2350, #2341, #2331, #2330, #2327, #2326).
waitForSubmissioncan't deadlock. It throws a clear error when called from inside the active turn (e.g. a tool that didrunTurn({ mode: "submit" })then waited, the pattern the in-turn error message recommended) or fromonSubmissionStatusfor a submission that can't settle until the hook returns. Docs and the in-turn error message are updated.runningemits; the abort controller is registered before therunninghook fires.resetTurnState, waiters on skipped submissions resolve only after their hook ran.activeChannelis gated likeactiveTurn, so leftover tool work from one turn no longer routes notices to a later turn's channel.autoContinue: falsemid-stream now drops the stale pending-state text once the parking turn finishes.dropGenerationAfterToolCalldrops only the paused tool's own step and the step reacting to it, keeping later steps' text._lastTurnChannelis set only when a turn starts inference and is cleared on reset.Not fixed:
messagesAppliedfallback misreporting when a submission reuses an existing message id; the stored data can't reliably distinguish a reused id from a fresh write, and a wrong answer would affect recovery replay.Verified on a combined branch with the other review follow-ups:
pnpm run check, agents chat/workers/react, ai-chat, and Think suites all pass.