You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
think: runTurn({ mode: "wait" }) resolves with message: undefined when a second turn is queued on the same agent #2243
runTurn({ mode: "wait" }) reports the finished turn's assistant message by reading the session's newest message after the turn queue has released its slot. When a second turn is already queued on the same agent, that queued turn can append its user message first, so the completed turn resolves with status: "completed" and message: undefined.
_runTurnWait awaits _runProgrammaticMessagesTurn (which holds the TurnQueue slot) and only then calls _enrichTurnResult, so the lookup happens outside the serialized region:
TurnQueue.enqueue (agents/dist/chat/index.js 164–189) releases in a finally before the caller's continuation runs, so the next turn's execute — which begins by appending its user message — is free to interleave.
The turn itself is fine: both turns run, both messages persist, and the session is correct afterwards. Only the reported message is wrong, which makes a correct turn look like a failure to the caller.
Cloudflare Workers / workerd, Think subclass in a Durable Object
Same code on main: packages/think/src/think.ts_enrichTurnResult
Reproduction
Two RPC calls on one agent instance, each calling runTurn({ mode: "wait" }) with a function input:
awaitPromise.all([stub.appendTurn({q: "any 2 bedrooms?"}),stub.appendTurn({q: "book a tour"}),]);
Probe inside the first call's continuation:
{"status":"completed","leafRole":"user"}
Expected leafRole: "assistant" for the turn that just completed; the leaf is the other turn's question. Reproduces reliably on a cold install and intermittently otherwise — it is a timing race, so it surfaces as a flaky test or an occasional 5xx in production rather than a hard failure.
Suggested fix
Resolve the assistant message from the run that produced it rather than from session-global state — either capture the leaf inside the admitted turn body (still holding the queue slot) and pass it out with the result, or look it up by the turn's requestId. Reading getLatestLeaf() after the slot is released is only correct when exactly one turn ever exists.
Workaround
Callers can record the id of the user message they submit and take the first assistant message after it in getMessages(), which is stable regardless of what else is queued.
A fix for this is on https://github.com/L4XB/agents/tree/fix/think-run-turn-wait-message (the queued turn reads its assistant leaf while it still holds the turn-queue slot and carries it in its result; _enrichTurnResult uses that instead of the post-release lookup; changeset + tests included). GitHub refuses to let me open the pull request against this repository (CreatePullRequest permission error, both from a fresh fork and a renamed one), so if a maintainer can open it or lift the restriction, it is ready.
Activity
L4XB commentedon Sep 11, 2026
A fix for this is on https://github.com/L4XB/agents/tree/fix/think-run-turn-wait-message (the queued turn reads its assistant leaf while it still holds the turn-queue slot and carries it in its result;
_enrichTurnResultuses that instead of the post-release lookup; changeset + tests included). GitHub refuses to let me open the pull request against this repository (CreatePullRequestpermission error, both from a fresh fork and a renamed one), so if a maintainer can open it or lift the restriction, it is ready.