openclaw - 💡(How to fix) Fix [Bug]: Telegram `streaming.mode: "partial"` and `"block"` duplicate the full preview when reply >4096 chars

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…

On Telegram, channels.telegram.streaming.mode: "partial" (the documented default) duplicates the entire preview content as a separate final-emit when the reply exceeds Telegram's per-message size cap (~4096 chars). "block" mode has the same root-cause bug shifted to the start (first chunk duplicated). Only "progress" mode delivers cleanly. This makes any long assistant reply on Telegram either visually broken (preview replayed in full) or unobservable while generating (progress mode is quiet until final).

Root Cause

Root cause (best guess from reading dist)

Fix Action

Workaround

Use streaming.mode: "progress" for Telegram. Trade-off: no incremental visible streaming for pure-text replies (the progress draft only updates on tool calls), so the user sees ~10-30s of "quiet" generation time before the final answer lands.

Code Example

{
     "mode": "partial",
     "chunkMode": "length"
   }

---
RAW_BUFFERClick to expand / collapse

Bug type

Regression (worked before, now fails)

Beta release blocker

No

Summary

Telegram streaming.mode: "partial" and "block" duplicate the full preview when reply >4096 chars

Summary

On Telegram, channels.telegram.streaming.mode: "partial" (the documented default) duplicates the entire preview content as a separate final-emit when the reply exceeds Telegram's per-message size cap (~4096 chars). "block" mode has the same root-cause bug shifted to the start (first chunk duplicated). Only "progress" mode delivers cleanly. This makes any long assistant reply on Telegram either visually broken (preview replayed in full) or unobservable while generating (progress mode is quiet until final).

Affected versions

  • OpenClaw gateway (verified on /opt/homebrew/lib/node_modules/openclaw/dist/index.js, build with dist/bot-DVHbpZjZ.js)
  • Telegram channel
  • Any reply that produces more than one Telegram message after chunking

Reproducer

  1. Set channels.telegram.streaming in ~/.openclaw/openclaw.json:
    {
      "mode": "partial",
      "chunkMode": "length"
    }
    (with textChunkLimit: 3800, but any value below ~4096 reproduces)
  2. Restart gateway: openclaw gateway restart
  3. Send the bot a prompt that produces a single assistant reply of ~6000 characters with no tool calls (e.g., "tell me a long story, at least 6000 characters")

Observed (5 messages delivered to the user)

  • chunk 1: preview part 1, sent as a new Telegram message and edited in place via editMessageText as the model streams
  • chunk 2: preview part 2, spawned automatically when the preview exceeded Telegram's per-message size cap; also edited in place as streaming continues
  • chunk 3: final-emit part 1, sent as a new message duplicating chunk 1's content verbatim
  • chunk 4: final-emit part 2, sent as a new message duplicating chunk 2's content verbatim
  • chunk 5: final-emit part 3, the closing section of the reply (this part is correct — it was generated after the preview last edited, so it legitimately needs a new message)

Expected

The final emit should detect that chunks 1 and 2 already exist verbatim in the preview message stream and skip re-sending them, only emitting chunk 3 (the new content beyond the last preview edit). Net result should be 3 Telegram messages, not 5.

Root cause (best guess from reading dist)

createLaneTextDeliverer in dist/bot-DVHbpZjZ.js (lines ~5063–5124) has a preview-finalized outcome that progress mode uses to tell the dispatcher "the preview already contains the final content, do not enqueue another final emit." partial and block modes never set this outcome, so the dispatcher always enqueues a fresh final-emit pass over all chunks regardless of what the preview already contains.

Suggested fix

In partial and block modes, after the last preview edit completes:

  1. Compare the final chunk list to the preview message contents.
  2. For each preview message whose content is a prefix-match of a final chunk, mark that lane finalized and short-circuit the dispatcher.
  3. Only emit new final messages for chunks not already covered by the preview.

This is essentially the same hand-off that progress mode already implements for the single-message case, generalized to N preview messages.

Workaround

Use streaming.mode: "progress" for Telegram. Trade-off: no incremental visible streaming for pure-text replies (the progress draft only updates on tool calls), so the user sees ~10-30s of "quiet" generation time before the final answer lands.

Environment

  • Host: macOS Darwin 24.6.0 arm64
  • Node: v25.9.0
  • OpenClaw: installed via Homebrew at /opt/homebrew/lib/node_modules/openclaw
  • Telegram bot via Bot API polling

Evidence

User reported the exact 5-chunk pattern with full message contents reconstructed. Both preview messages and both final-emit duplicate messages are byte-identical to the user. Repro is deterministic on every long reply.


Source of bug investigation: Strike (Founder & CEO) / Santiago Rosenblatt

Steps to reproduce

Environment:

• OpenClaw gateway, Telegram channel via Bot API polling • Reproduced on macOS Darwin 24.6.0 arm64, Node v25.9.0, OpenClaw installed via Homebrew at /opt/homebrew/lib/node_modules/openclaw

Config under test — ~/.openclaw/openclaw.json:

{ "channels": { "telegram": { "enabled": true, "textChunkLimit": 3800, "streaming": { "mode": "partial", "chunkMode": "length" } } } }(textChunkLimit below Telegram's ~4096 cap is required so the reply chunks; otherwise the bug doesn't surface)

Repro steps:

  1. Set the config above.
  2. Restart the gateway: openclaw gateway restart.
  3. From a Telegram DM the bot listens to, send a prompt that forces a long single-turn reply with no tool calls, e.g.: "Tell me a long story, at least 6000 characters, no questions back."
  4. Observe how the assistant reply renders in the Telegram client.

Expected:

• One preview message that edits in place as the model streams. • If the final reply exceeds the chunk limit (3800 chars), the preview is "promoted" to chunk 1 of the final answer, and only the remaining chunks (chunk 2, chunk 3, …) are sent as new messages. • Total messages = ceil(final_length / textChunkLimit).

Observed (full-message duplication): For a ~6000-char reply at textChunkLimit: 3800 the Telegram client receives 5 messages instead of 3:

• chunk 1 — preview part 1 (in-place edits, ends when the preview overflows the ~4096 Telegram per-message cap) • chunk 2 — preview part 2 (a second growing message, also in-place edits) • chunk 3 — NEW message byte-identical to chunk 1 (final-emit replays chunk 1) • chunk 4 — NEW message byte-identical to chunk 2 (final-emit replays chunk 2) • chunk 5 — final-emit chunk 3 (the tail content that wasn't in the preview — this one is correct and expected)

So the entire preview is replayed as new messages at the end. For any reply >4096 chars, the user sees the full content roughly twice.

Same root-cause variant in block mode: with "mode": "block", the duplicate appears at the START (first chunk sent twice back-to-back) instead of at the end. Same missing preview-finalized dispatcher short-circuit.

Workaround: set "mode": "progress" — only mode that delivers cleanly. Trade-off: no incremental visible text for pure-text replies; the user sees ~10–30s of "typing/working" status before the final answer lands as one or more clean messages.

Root-cause hint for the issue body

Reading dist/bot-DVHbpZjZ.js around lines 5063–5124, createLaneTextDeliverer has a preview-finalized outcome that "progress" mode uses to tell the dispatcher "the preview already contains the final content, do not enqueue a fresh final emit." "partial" and "block" never set this outcome, so the dispatcher always runs a full final-emit pass over every chunk regardless of what already exists in the preview stream.

Suggested fix: in partial and block, after the last preview edit completes, mark each preview-occupied chunk as preview-finalized so the dispatcher only emits chunks that genuinely contain new content. For block this means chunk 1 (preview) is finalized; for partial with multi-message preview overflow, this means all preview messages are finalized.

Expected behavior

For a single-turn assistant reply on Telegram in streaming.mode: "partial":

  1. During generation: OpenClaw sends one preview message and edits it in place via editMessageText as the model streams output. If the cumulative streamed text exceeds Telegram's per-message size cap (~4096 chars), OpenClaw spawns additional preview messages and continues editing the latest one in place.

  2. At completion (final emit): OpenClaw promotes the existing preview message(s) into the final answer rather than re-sending their content as new messages. Specifically:

• For each preview message whose content is a verbatim prefix of a final chunk, that preview message becomes the final chunk in place. No new message is sent for it. • Only chunks containing content that was NOT in the preview at last-edit time are sent as new messages. 3. Total message count = ceil(final_reply_length / textChunkLimit) — same number of Telegram messages the user would receive if streaming were disabled and the final answer was sent in one atomic chunked dispatch.

Concrete example — for a 6000-char final reply with textChunkLimit: 3800:

• Expected: 2 Telegram messages total (3800 + 2200 chars), both starting their lives as preview messages and finalized in place. • Currently observed in partial mode: 5 Telegram messages total (preview replayed in full as new final-emit messages).

This is already what progress mode does for its single-message status draft case (it has a preview-finalized lane outcome that short-circuits the dispatcher). The bug is that partial and block never set that outcome, so the dispatcher always emits a fresh final pass over every chunk regardless of what's already in the preview stream.

The fix is to generalize the preview-finalized short-circuit to multi-message preview overflow in partial, and to apply it to the first chunk in block.

Actual behavior

For a single-turn assistant reply on Telegram in streaming.mode: "partial" whose final length exceeds Telegram's per-message size cap (~4096 chars), the user receives the entire preview content again as new messages at the end. The reply is delivered roughly twice.

Concrete observation — 6000-char story reply with textChunkLimit: 3800, no tool calls:

User received 5 Telegram messages in this order:

  1. chunk 1 — preview message #1. Created at stream start and edited in place via editMessageText as the model streamed. Stopped editing when it hit Telegram's ~4096-char per-message cap. Contains roughly the first 4000 chars of the reply.

  2. chunk 2 — preview message #2. Created when chunk 1 hit the size cap. Continued to be edited in place via editMessageText until the model finished generating. Contains roughly chars 4000 through ~5800 of the reply.

  3. chunk 3 — final-emit message #1. Sent as a new Telegram message. Byte-identical to chunk 1's content. The first ~3800 chars of the reply, sent again.

  4. chunk 4 — final-emit message #2. Sent as a new Telegram message. Byte-identical to chunk 2's content. Roughly chars 3800 through 5800 of the reply, sent again.

  5. chunk 5 — final-emit message #3. Sent as a new Telegram message. Contains the trailing content (roughly chars 5800–6000) that was generated after the preview last edited and therefore was not in the preview stream. This message alone is the correct/expected new content.

So messages 3 and 4 are pure duplicates of messages 1 and 2. The user sees the reply twice in their Telegram client, ~30 seconds apart.

Variant in streaming.mode: "block" — duplicate shifts to the start: first chunk is sent once as a preview block, then once again as a new message at the final emit. Same root cause (missing preview-finalized dispatcher short-circuit), different surface.

Variant in streaming.mode: "progress" — no duplicates. The progress draft message is cleared at completion and the final reply is sent as a normal atomic chunked dispatch. This is the only mode that delivers correctly on Telegram, at the cost of no incremental visible streaming for pure-text replies (the progress draft only animates on tool calls, so the user sees ~10–30s of "typing/working" status before the final answer lands).

Reproducibility: deterministic. Every long reply on partial produces the duplicate pattern. Reproduced multiple times in a single session 2026-05-28 08:50–08:59 ART.

OpenClaw version

OpenClaw 2026.5.12 (f066dd2) Installed via Homebrew at /opt/homebrew/lib/node_modules/openclaw.

Operating system

macOS 15.6 (Sequoia), build 24G84 Darwin kernel 24.6.0, arm64 (Apple Silicon — Mac mini) Node.js v25.9.0 Telegram channel: Bot API polling (no webhook).

Install method

No response

Model

anthropic/claude-opus-4-7

Provider / routing chain

Telegram client (Santiago) -> OpenClaw gateway 2026.5.12 -> claude-opus-4-7 -> anthropic Messages API -> OpenClaw deliverer: createLaneTextDeliverer -> Telegram Bot API -> Telegram client (Santiago) → 5 messages, content duplicated

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Impact and severity

No response

Additional information

No response

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…

FAQ

Expected behavior

For a single-turn assistant reply on Telegram in streaming.mode: "partial":

  1. During generation: OpenClaw sends one preview message and edits it in place via editMessageText as the model streams output. If the cumulative streamed text exceeds Telegram's per-message size cap (~4096 chars), OpenClaw spawns additional preview messages and continues editing the latest one in place.

  2. At completion (final emit): OpenClaw promotes the existing preview message(s) into the final answer rather than re-sending their content as new messages. Specifically:

• For each preview message whose content is a verbatim prefix of a final chunk, that preview message becomes the final chunk in place. No new message is sent for it. • Only chunks containing content that was NOT in the preview at last-edit time are sent as new messages. 3. Total message count = ceil(final_reply_length / textChunkLimit) — same number of Telegram messages the user would receive if streaming were disabled and the final answer was sent in one atomic chunked dispatch.

Concrete example — for a 6000-char final reply with textChunkLimit: 3800:

• Expected: 2 Telegram messages total (3800 + 2200 chars), both starting their lives as preview messages and finalized in place. • Currently observed in partial mode: 5 Telegram messages total (preview replayed in full as new final-emit messages).

This is already what progress mode does for its single-message status draft case (it has a preview-finalized lane outcome that short-circuits the dispatcher). The bug is that partial and block never set that outcome, so the dispatcher always emits a fresh final pass over every chunk regardless of what's already in the preview stream.

The fix is to generalize the preview-finalized short-circuit to multi-message preview overflow in partial, and to apply it to the first chunk in block.

Still need to ship something?

×6

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

Back to top recommendations

TRENDING