Skip to content

Telegram: duplicate final-reply blocks when streaming.mode="partial" and blockStreaming is off (regression in 5.12) #82329

@Veda-openclaw

Description

@Veda-openclaw

Version

OpenClaw 2026.5.12 (upgraded from 2026.5.7)

Symptom

Every Telegram reply lands as 2–4 duplicate sendMessage calls in a row. The draft-stream preview message edits in place as expected during the turn, then the final reply is delivered N times (where N = number of assistant blocks emitted during the turn).

Gateway log shows the duplicates clearly:

[telegram] sendMessage ok chat=<id> message=36645
[telegram] sendMessage ok chat=<id> message=36646
[telegram] sendMessage ok chat=<id> message=36647
[telegram] sendMessage ok chat=<id> message=36648

Four sends in 4 seconds for a single assistant reply.

Pre-5.12 (5.7): single sendMessage per reply.

Reproduction

  1. Configure Telegram with channels.telegram.streaming.mode: "partial" and leave blockStreaming and agents.defaults.blockStreamingDefault unset/off.
  2. Send any message that produces an assistant turn with multiple text blocks (e.g. a checklist or multi-paragraph response).
  3. Observe N duplicate final messages on Telegram.

Config

"channels": {
  "telegram": {
    "streaming": { "mode": "partial" }
    // no blockStreaming, no blockStreamingCoalesce, no textChunkLimit
  }
}
// agents.defaults.blockStreamingDefault not set (defaults to "off")

Root cause

In agent-runner.runtime-*.js (line ~2381-2454), the final-payload dedupe branch only suppresses already-streamed blocks when blockStreamingEnabled === true OR directlySentBlockKeys.size > 0:

const shouldDropFinalPayloads = params.blockStreamingEnabled && Boolean(params.blockReplyPipeline?.didStream()) && !params.blockReplyPipeline?.isAborted();
// ...
const contentSuppressedPayloads = shouldDropFinalPayloads ? (() => {
    // branch 1: blockStreaming on + did stream -> drop except unsent media
})() : params.blockStreamingEnabled ? (() => {
    // branch 2: blockStreaming on, didnt stream -> use hasSentPayload
})() : params.directlySentBlockKeys?.size ? (() => {
    // branch 3: directly sent media blocks -> subtract by key
})() : dedupedPayloads;   // <-- branch 4: ALL payloads survive unfiltered

With streaming.mode: "partial" and blockStreamingDefault: "off" (default), blockStreamingEnabled = false. The draft-stream preview runs and edits a single message in place, but each text block also lands in dedupedPayloads. Branch 4 fires, all payloads survive, and sendFinalPayload in dispatch-*.js runs once per payload.

Likely related to PRs #79621/#79986 and the rich-reply content group of fixes in 5.12 that expanded what counts as deliverable content without widening the dedupe gate to cover non-blockStreaming partial-stream users.

Expected

A single final reply per assistant turn, matching pre-5.12 behavior.

Suggested fix

Extend the dedupe gate to cover preview-stream users, e.g.:

: (params.blockStreamingEnabled || params.previewStreamingActive) ? ... : ...

Workaround

  • agents.defaults.blockStreamingDefault: "on" (activates branch 2 dedup)
  • channels.telegram.streaming.mode: "off" (disables preview stream entirely)

Metadata

Metadata

Assignees

No one assigned

    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