Skip to content

Fix duplicate assistant messages during overlapping submits#1232

Merged
threepointone merged 3 commits intomainfrom
fix/issue-1231-overlapping-submit-duplicates
Mar 31, 2026
Merged

Fix duplicate assistant messages during overlapping submits#1232
threepointone merged 3 commits intomainfrom
fix/issue-1231-overlapping-submit-duplicates

Conversation

@whoiskatrin
Copy link
Copy Markdown
Contributor

@whoiskatrin whoiskatrin commented Mar 30, 2026

Summary

  • Keep the active local assistant message pinned as the internal tail entry while an overlapping submit is sent, so AI SDK stream writes keep replacing the in-flight assistant instead of appending a duplicate
  • Preserve that protected assistant when CF_AGENT_CHAT_MESSAGES broadcasts arrive mid-stream, then restore the original message ordering once the stream completes
  • Use an anchor-based restore (the ID of the preceding message) instead of a numeric index, so the assistant returns to the correct position even if the server injects or reorders messages during the stream
  • Clear protection state in the useEffect cleanup to prevent leaks on unmount or effect re-runs
  • Add six React regression tests covering the core overlap scenario, multiple server broadcasts, CF_AGENT_CHAT_CLEAR mid-protection, anchor-based restore with server-injected messages, done:true without a prior start chunk, and the not-streaming guard

Closes #1231

Testing

npm run test:react -w @cloudflare/ai-chat -- --run
npx oxfmt --check packages/ai-chat/src/react.tsx packages/ai-chat/src/react-tests/use-agent-chat.test.tsx
npx oxlint packages/ai-chat/src/react.tsx packages/ai-chat/src/react-tests/use-agent-chat.test.tsx

Reviewer Notes

  • The root issue is in AI SDK chat state: stream updates replace the last message only while the in-flight assistant is still the array tail. An overlapping submit inserts a user message after it, so later chunks append a second assistant with the same ID.
  • This fix stays entirely in useAgentChat; it does not change server persistence or messageConcurrency semantics.
  • The protection is intentionally temporary: we only keep the streaming assistant at the tail while the local request is active, then restore the original order so the visible conversation stays user, assistant, user between turns.
  • Restore uses an anchor message ID (the message before the assistant) rather than a numeric index. This is robust against insertions, deletions, or reorderings that happen between protection and restore (e.g., server-injected system messages or cross-tab broadcasts).
  • Complementary to fix(ai-chat): strip messageId from continuation start chunks (#1229) #1235 (4f79280) which fixes duplicate assistants during continuations server-side; this PR fixes duplicate assistants during overlapping submits client-side. No overlap — different files, different layers, different triggers.

Open with Devin

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 30, 2026

🦋 Changeset detected

Latest commit: 19aaf9c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/ai-chat Patch

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

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 30, 2026

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@1232

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@1232

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@1232

hono-agents

npm i https://pkg.pr.new/hono-agents@1232

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@1232

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@1232

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@1232

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@1232

commit: 19aaf9c

whoiskatrin and others added 2 commits March 31, 2026 13:59
Refactor streaming-protection logic and add robust tests for edge cases. Renamed moveMessageById to moveMessageToEnd and simplified its behavior to always move a message to the end. Introduced anchorMessageId in protectedStreamingAssistant to remember the message that an in-progress assistant should be anchored to, and updated restore logic to reinsert the assistant either after the anchor or at the front if no anchor exists. Consolidated local response ID handling inside the effect (localResponseIds) and ensure IDs are cleared on CF_AGENT_CHAT_CLEAR; also clear protectedStreamingAssistant on cleanup. Tests: wrap sendMessage calls in act, and add multiple new tests covering multiple server broadcasts during a stream, CF_AGENT_CHAT_CLEAR mid-stream, server-inserted messages that require anchor-based restoration, handling done without a start chunk, and verifying no protection is activated when not streaming.
@threepointone threepointone force-pushed the fix/issue-1231-overlapping-submit-duplicates branch from 3598823 to 89a4d88 Compare March 31, 2026 13:36
@threepointone threepointone marked this pull request as ready for review March 31, 2026 13:37
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@threepointone threepointone merged commit 2713c45 into main Mar 31, 2026
2 checks passed
@threepointone threepointone deleted the fix/issue-1231-overlapping-submit-duplicates branch March 31, 2026 13:53
@github-actions github-actions bot mentioned this pull request Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

messageConcurrency strategies cause duplicate assistant message during active stream

2 participants