Skip to content

2-Line Fix - useUIMessages: streaming duplicates caused by dedup running after combineUIMessages #276

@mainpingdream

Description

@mainpingdream

Hey! Just so you know this was written by Claude, but it's a super simple 2-line change, and it fixed some rendering of messages for me when I initiated a retry.

Thanks for the great package! Just thought I would let you all know.

---- Claude Notes ----

Bug

useUIMessages shows duplicate message content during streaming. For example, when an agent counts to 10 with tool calls between each number, the UI briefly shows entries like 7, 8, 7 + RunCode, 8 + RunCode — the text appears once without its tool call, then again with it.

The duplicates disappear once the stream finishes and only persisted messages remain.

Root cause

In useUIMessages, combineUIMessages runs before dedupeMessages:

const combined = combineUIMessages(sorted(paginated.results));
return { ...paginated, results: dedupeMessages(combined, streamMessages ?? []) };

combineUIMessages merges all adjacent assistant messages at the same order into a single UIMessage, collapsing its stepOrder to the first message's value (e.g. stepOrder: 1). When dedupeMessages then tries to match a streaming message at stepOrder: 13 against the combined message at stepOrder: 1, they don't match — so both are kept.

Fix

Swap the order — dedup first (while each persisted message still has its original stepOrder), then combine:

const deduped = dedupeMessages(sorted(paginated.results), streamMessages ?? []);
return { ...paginated, results: combineUIMessages(deduped) };

This is a two-line change in src/react/useUIMessages.ts.

Reproduction

Any multi-step agent generation with saveStreamDeltas: true where the LLM produces text + tool calls in each step. The duplicates appear transiently during streaming as each step transitions from streaming to persisted.

Easiest repro: prompt the agent to "count to 10, saying each number then calling a tool to sleep for 1 second before the next."

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions