Repository navigation
fix(chat): release useAgentChat streaming protection when the socket closes - #2496
Merged
Merged
Conversation
🦋 Changeset detectedLatest commit: 0e491a7 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 |
Contributor
|
✅ agents import sizes: no significant changes ( |
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
useAgentChat protects a locally streaming assistant from behind snapshots until that request's done/error frame. A WebSocket close ends the local stream without that frame, so the protection stayed armed and swapped the cut-off local copy into every later cf_agent_chat_messages snapshot. The stored reply never showed, and the next send shipped the partial copy. Release the protection on close. A resumed replay re-arms it from its start chunk, so mid-stream resume still neither doubles nor drops text. Refs #2464
ben-reitz
force-pushed
the
fix/use-agent-chat-stale-reply-after-close
branch
from
October 5, 2026 20:17
e337d9c to
6a5f1f5
Compare
Merged
tombeckenham
added a commit
to tombeckenham/agents-tom
that referenced
this pull request
Oct 7, 2026
Brings react-agui.tsx level with the legacy hook in agents/chat/react, which received these through the upstream merge: - cloudflare#2344 the server snapshot heals a diverged observed assistant - cloudflare#2348 an observer skips replayed continuation frames it already applied - cloudflare#2361 onToolCall fires only after the stream ends - cloudflare#2378 onTurnEnd fires once per ended request - cloudflare#2383 turns settle across replay, resume and observers (held and recovering requests, the resume:false idle probe) - cloudflare#2496 streaming protection is released when the socket closes - cloudflare#2394 follow the socket when the agent name changes - cloudflare#2403 the tool-result cleanup no longer dispatches per chunk - cloudflare#2039 keep an optimistic send the reconnect transcript omits The last three had no failing test: their upstream tests are ported into react-tests (the address-change one against a fake of useAgent's pending socket, since this package has no test worker) and failed before the fix. restoreBufferedSends, MAX_REMEMBERED_ENDED_TURNS and isSocketAddressPending are exported from agents/chat/react so the two hooks share them.
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.
Part of #2464, the client side. This fully fixes the bug for Think. AIChatAgent still needs its server-side change; see Merge Danger.
Summary
useAgentChatprotects a locally streaming assistant reply so that stale snapshots can't replace it. Only that request's done/error frame released the protection. When the socket closes mid-stream, the transport ends the local stream and that frame never arrives. The protection stayed armed, so every latercf_agent_chat_messagessnapshot had the cut-off local copy swapped back in. That included the full reply Think sends when a client reconnects between turns. The next send then shipped the cut-off copy to the server.onAgentClose() sawClose = true serverReportsRecovery = false fallbackAckedResumeRequestIds.clear() + protectedStreamingAssistantRef.current = null # the done/error frame won't comeIf the turn is still running on reconnect, the server's replayed
startchunk re-arms protection. The existing replay reset de-duplicates the text, and it doesn't depend on protection.Alternatives I rejected:
resume_noneidle: too late. Think's idle snapshot arrives before the probe answer, so the snapshot has already been overwritten, and this missesresume: false.Evidence
New tests in
packages/ai-chat/src/react-tests/use-agent-chat.test.tsx, in the describe block "useAgentChat stale reply after a mid-stream close (#2464)":HelHelThe three rows that pass on main guard against the fix breaking resume. Suites: ai-chat react 106/106, agents react 168/168, agents chat unit 615/615, Think react 2/2.
pnpm run checkpasses.Merge Danger
Door: two-way
The change is one line.
Blast Radius: chat clients
Every
useAgentChatuser, on both Think and AIChatAgent. The only path that changes is a socket close during a local stream; resume replays still re-arm protection.Still open in #2464:
Already on main, not changed here: sending a second message mid-stream, then a close and a resume, duplicates the first reply.