openclaw - 💡(How to fix) Fix WebChat: duplicate message rendering after streaming completes [3 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#72963Fetched 2026-04-28 06:29:27
View on GitHub
Comments
3
Participants
3
Timeline
5
Reactions
0
Author
Timeline (top)
commented ×3cross-referenced ×2

When using the WebChat (Control UI), assistant messages appear twice after a response completes. The first copy is the streaming version, the second is the full message fetched via chat.history.

Root Cause

The message dedup logic in the Control UI frontend fails because streaming messages have different IDs than the final complete message.

Code Example

function el(e) {
  let role = g(e.role);
  let text = bc(e)?.trim();
  if (text) return `${role}:text:${text}`;
  return `${role}:content:${JSON.stringify(e.content)}`;
}
RAW_BUFFERClick to expand / collapse

Description

When using the WebChat (Control UI), assistant messages appear twice after a response completes. The first copy is the streaming version, the second is the full message fetched via chat.history.

Root Cause

The message dedup logic in the Control UI frontend fails because streaming messages have different IDs than the final complete message.

The merge function (tl())

When a streaming run completes (state === "final"), the frontend calls chat.history to fetch the full message list, then merges it with already-rendered streaming messages using tl().

The ID function (el())

Message dedup IDs are generated as:

function el(e) {
  let role = g(e.role);
  let text = bc(e)?.trim();
  if (text) return `${role}:text:${text}`;
  return `${role}:content:${JSON.stringify(e.content)}`;
}

The ID is based on the full text content of the message. During streaming, the text grows incrementally, so the streaming message and the final message from chat.history produce different IDs. The dedup in tl() fails, and the complete message is appended after the streaming message — resulting in the same content appearing twice.

Steps to Reproduce

  1. Open WebChat (Control UI)
  2. Send any message that produces a multi-sentence response
  3. Observe: the response appears normally during streaming, then the full text appears again below when the run completes

Expected Behavior

Only one copy of each message should be displayed.

Environment

  • OpenClaw: v2026.4.25
  • Channel: WebChat (Control UI)
  • Browser: Chrome 147+
  • OS: macOS (ARM64)

Suggested Fix

Use a stable message identifier (e.g., message index or a server-assigned message ID) for dedup instead of content-based hashing. Alternatively, when chat.history response arrives, replace the streaming messages entirely rather than merging.

Relevant Code

File: dist/control-ui/assets/index-BUB744L7.js

  • tl() — merge function that attempts dedup
  • el() — generates message IDs from content text
  • ol()chat.history handler that calls tl(newMessages, existingMessages)
  • TF() — streaming event handler

Related: this may be more noticeable with longer responses where the streaming text differs significantly from the final text.

extent analysis

TL;DR

Use a stable message identifier or replace streaming messages with the full message from chat.history to fix message duplication.

Guidance

  • Identify a stable message identifier, such as a message index or server-assigned message ID, to use for deduplication instead of content-based hashing.
  • Consider replacing the streaming messages entirely with the full message from chat.history when the response completes, rather than merging them.
  • Review the tl() merge function and el() ID generation function to understand how they contribute to the duplication issue.
  • Test the fix with longer responses where the streaming text differs significantly from the final text to ensure the issue is fully resolved.

Example

// Example of using a server-assigned message ID for deduplication
function el(e) {
  return e.messageId; // assuming messageId is a unique identifier assigned by the server
}

Notes

The provided fix suggestions assume that the chat.history response contains the necessary information to replace or merge the streaming messages correctly. Additional testing and verification may be necessary to ensure the fix works as expected in all scenarios.

Recommendation

Apply the workaround of replacing streaming messages with the full message from chat.history when the response completes, as this approach is more straightforward to implement and less prone to errors.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix WebChat: duplicate message rendering after streaming completes [3 comments, 3 participants]