Skip to content

fix(telegram): deduplicate MEDIA attachments in non-streaming mode#78420

Merged
obviyus merged 5 commits into
openclaw:mainfrom
rogerdigital:fix/telegram-media-duplicate-non-streaming
May 10, 2026
Merged

fix(telegram): deduplicate MEDIA attachments in non-streaming mode#78420
obviyus merged 5 commits into
openclaw:mainfrom
rogerdigital:fix/telegram-media-duplicate-non-streaming

Conversation

@rogerdigital

@rogerdigital rogerdigital commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Non-streaming Telegram delivers each MEDIA: attachment twice — once from the media-only block reply and once from the final reply
  • Track media URLs sent via block replies in a Set, then filter duplicates from final reply payloads
  • Extract deduplicateBlockSentMedia into a standalone pure function for testability
  • Clear legacy mediaUrl fallback when all mediaUrls are deduped, preventing resolvePayloadMediaUrls from resending via the single-URL path

Closes #78372

Root cause

When streaming: "off", the deliver callback in bot-message-dispatch.ts receives both:

  1. A block reply with mediaUrls populated (sent directly via sendDirectBlockReply in reply-delivery.ts:160, which sends media-only blocks even when streaming is off)
  2. A final reply with the same mediaUrls (the MEDIA: directive text persists in the agent's complete output)

Unlike WebChat (which has appendedWebchatAgentMedia guard at chat.ts:2512) or the streaming path (which deduplicates via blockReplyPipeline), Telegram's non-streaming deliver callback had no mechanism to detect that the same media was already delivered.

Changes

  • bot-message-dispatch.ts: Add sentBlockMediaUrls tracking set inside runDispatch. In the deliver callback, record block-reply media URLs and call deduplicateBlockSentMedia for final replies. All downstream usage switches from payload to effectivePayload.
  • bot-message-dispatch.media-dedup.ts (new): Pure function deduplicateBlockSentMedia — returns deduplicated payload, or undefined to skip entirely. Also clears legacy mediaUrl when all mediaUrls entries are removed.
  • bot-message-dispatch.media-dedup.test.ts (new): 9 test cases covering no-media, no-overlap, partial overlap, full overlap with/without text, and legacy mediaUrl clearing.

Real behavior proof

Behavior or issue addressed: Non-streaming Telegram bot delivers each MEDIA attachment twice — once via block reply, once in final reply. Fixes #78372.

Real environment tested: OpenClaw 2026.5.6, macOS 15.5, Node 22, gateway restarted after build, real Telegram bot in non-streaming mode.

Exact steps or command run after fix:

  1. pnpm build
  2. openclaw gateway restart
  3. Sent message to Telegram bot asking it to search for a cat image and send it

Evidence after fix:

telegram_dedup_proof

Observed result after fix: Image delivered exactly once via block reply. Final reply contained only text — no duplicate media. No errors in gateway logs.

What was not tested: Streaming mode (uses separate blockReplyPipeline dedup path, not affected by this change). Multi-image payloads in live environment (covered by unit tests).

Test plan

  • Unit tests pass: pnpm test extensions/telegram/src/bot-message-dispatch.media-dedup.test.ts — 9/9
  • Broader telegram dispatch tests: pnpm test extensions/telegram/src/bot-message-dispatch.test.ts — 45/45
  • Format check passes: pnpm exec oxfmt --check on all changed files
  • Real behavior proof: Telegram non-streaming media dedup verified (see above)
  • Full extension suite: pnpm test extensions/telegram

@openclaw-barnacle openclaw-barnacle Bot added channel: telegram Channel integration: telegram triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. size: S labels May 6, 2026
@clawsweeper

clawsweeper Bot commented May 6, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Summary
The PR adds Telegram turn-scoped block-media URL tracking, a pure final-payload media dedupe helper, regression tests, and a changelog entry so non-streaming MEDIA attachments are not resent in the final reply.

Reproducibility: yes. source-reproducible. Current main directly sends non-streaming media block replies while final media suppression only imports sent media from blockReplyPipeline when block streaming is enabled, and #78372 supplies live Telegram symptoms.

Real behavior proof
Sufficient (screenshot): The PR body/comment include after-fix real Telegram proof, and the inspected screenshot directly shows the successful non-streaming path delivering one image instead of duplicates.

Next step before merge
No narrow ClawSweeper repair remains; maintainer handling should focus on exact-head CI/merge readiness rather than another automated code fix.

Security
Cleared: The diff is scoped to Telegram dispatch logic, colocated tests, and changelog text, with no dependency, workflow, credential, or package-resolution changes.

Review details

Best possible solution:

Land the Telegram-local dedupe after exact-head validation is green or failures are confirmed unrelated; keep the paired bug issue open until this PR merges.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible. Current main directly sends non-streaming media block replies while final media suppression only imports sent media from blockReplyPipeline when block streaming is enabled, and #78372 supplies live Telegram symptoms.

Is this the best way to solve the issue?

Yes. Keeping the fix in the Telegram dispatch owner module is the narrow maintainable boundary, and the latest head now gates dedupe on actual visible delivery with skipped/no-send fallback coverage.

Acceptance criteria:

  • pnpm test extensions/telegram/src/bot-message-dispatch.test.ts extensions/telegram/src/bot-message-dispatch.media-dedup.test.ts
  • pnpm exec oxfmt --check --threads=1 extensions/telegram/src/bot-message-dispatch.ts extensions/telegram/src/bot-message-dispatch.test.ts extensions/telegram/src/bot-message-dispatch.media-dedup.ts extensions/telegram/src/bot-message-dispatch.media-dedup.test.ts CHANGELOG.md
  • Resolve or confirm unrelated exact-head GitHub check failures before landing.

What I checked:

Likely related people:

  • obviyus: Recent file history shows multiple Telegram final reply, preview, and streamed text fixes in this delivery surface, and the latest PR head commit gates this dedupe on visible delivery. (role: recent Telegram dispatch contributor; confidence: high; commits: 10bbed8a6d30, 30e079dd89b4, 814b125f114c; files: extensions/telegram/src/bot-message-dispatch.ts, extensions/telegram/src/lane-delivery-text-deliverer.ts)
  • steipete: Recent history includes the bundled plugin message-lifecycle migration and shared reply/media dedupe changes that shape final media suppression contracts. (role: shared reply/media dedupe contributor; confidence: medium; commits: 05eda57b3c72, eb66def65604, 04c5bbf33d1b; files: extensions/telegram/src/bot-message-dispatch.ts, src/auto-reply/reply/agent-runner-payloads.ts, src/auto-reply/reply/reply-delivery.ts)
  • vincentkoc: Recent commits touched Telegram preview/final reply behavior and core reply helper paths adjacent to this dedupe behavior. (role: adjacent reply-delivery contributor; confidence: medium; commits: 8a2348547251, eee730789107, 1470b439e27c; files: extensions/telegram/src/bot-message-dispatch.ts, src/auto-reply/reply/agent-runner-payloads.ts)
  • Conan-Scott: Recent reply-delivery history includes the non-streaming TTS/audio media block delivery path that makes this Telegram dedupe necessary. (role: introduced adjacent non-streaming media block behavior; confidence: medium; commits: e437763246de; files: src/auto-reply/reply/reply-delivery.ts)

Remaining risk / open question:

  • Exact-head GitHub check-runs still show failures and one in-progress check; annotations point outside this Telegram diff, but landing still needs maintainer CI resolution or rerun.
  • No local tests were run because this was a read-only review; validation relies on the PR's supplied test claims, source inspection, and GitHub check metadata.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 337148a82100.

Re-review progress:

@rogerdigital

Copy link
Copy Markdown
Contributor Author

CI failure note: The 3 failing checks (checks-node-auto-reply-reply-dispatch, checks-node-core, Real behavior proof) are unrelated to this branch.

checks-node-auto-reply-reply-dispatch fails identically on the latest main (CI run on 9bcb56b4) with the same assertion error in dispatch-from-config.test.ts (expecting "message_tool_only" but receiving "automatic"). checks-node-core is the aggregate gate that fails because the above shard failed.

Real behavior proof is addressed separately — live Telegram verification incoming.

@rogerdigital

rogerdigital commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

Real behavior proof — Telegram non-streaming media dedup

Branch: fix/telegram-media-duplicate-non-streaming (commit 925b5d72cc)
Environment: OpenClaw 2026.5.4, macOS, gateway restarted after build
Telegram bot: non-streaming mode

Test: Asked bot to search for a cat image and send it.
Result: Image delivered exactly once via block reply. Final reply contained only text — no duplicate media. No errors.

Screenshot (after fix):
photo_screenshoot

@rogerdigital
rogerdigital force-pushed the fix/telegram-media-duplicate-non-streaming branch from 925b5d7 to 7869192 Compare May 7, 2026 06:31
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 7, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 7, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 7, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 7, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 7, 2026
@rogerdigital
rogerdigital force-pushed the fix/telegram-media-duplicate-non-streaming branch from 369f43c to 741034b Compare May 7, 2026 11:14
@rogerdigital
rogerdigital requested review from a team as code owners May 7, 2026 11:14
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: googlechat Channel integration: googlechat channel: imessage Channel integration: imessage channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: slack Channel integration: slack channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web channel: zalouser Channel integration: zalouser app: macos App: macos app: web-ui App: web-ui gateway Gateway runtime and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 7, 2026
@rogerdigital
rogerdigital force-pushed the fix/telegram-media-duplicate-non-streaming branch from 741034b to e3855dc Compare May 7, 2026 11:19
@openclaw-barnacle openclaw-barnacle Bot removed docs Improvements or additions to documentation channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: googlechat Channel integration: googlechat channel: imessage Channel integration: imessage channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: slack Channel integration: slack channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web channel: zalouser Channel integration: zalouser app: macos App: macos app: web-ui App: web-ui gateway Gateway runtime extensions: diagnostics-otel Extension: diagnostics-otel extensions: memory-core Extension: memory-core cli CLI command changes security Security documentation scripts Repository scripts labels May 7, 2026
@rogerdigital

Copy link
Copy Markdown
Contributor Author

CI failure note (updated): checks-node-core and checks-node-core-support-boundary fail identically on latest main (CI run on 64514a6548) — dist shard and core-support-boundary build-artifact failures unrelated to this branch.

Branch-specific Telegram tests pass locally: 45/45 in bot-message-dispatch.test.ts + bot-message-dispatch.media-dedup.test.ts.

@rogerdigital

Copy link
Copy Markdown
Contributor Author

Addressed the P2 finding (track block media only after successful delivery):

  • Moved sentBlockMediaUrls.add() from before sends to after successful completion — if a block send throws, the URL is not recorded, so final fallback retains the media attachment.
  • Added regression tests for both success-path dedup and failure-path media preservation.

Commit: 2c08700

All 108 CI checks pass, conflict resolved, ready for review.

@steipete — would appreciate a look when you have a moment.

rogerdigital and others added 5 commits May 10, 2026 08:42
Non-streaming Telegram delivers each MEDIA: attachment twice — once
from the media-only block reply and once from the final reply. Track
media URLs sent via block replies and filter duplicates from final
replies.

Closes openclaw#78372
Preserve the full ReplyPayload type through deduplication so that
channelData and other properties are not lost.
Move sentBlockMediaUrls recording from before sends to after successful
delivery completion. If a block send throws, the URL is not recorded,
so final fallback delivery retains the media attachment instead of
incorrectly deduplicating it.

Add regression tests for both success-path dedup and failure-path
media preservation.

Addresses ClawSweeper P2: block-failure → final-media-loss.
@obviyus

obviyus commented May 10, 2026

Copy link
Copy Markdown
Contributor

Landed via rebase onto main.

  • Scoped tests: pnpm test extensions/telegram/src/bot-message-dispatch.test.ts extensions/telegram/src/bot-message-dispatch.media-dedup.test.ts
  • Format: pnpm exec oxfmt --check --threads=1 extensions/telegram/src/bot-message-dispatch.ts extensions/telegram/src/bot-message-dispatch.test.ts extensions/telegram/src/bot-message-dispatch.media-dedup.ts extensions/telegram/src/bot-message-dispatch.media-dedup.test.ts CHANGELOG.md
  • Live Telegram proof: script=$(ls -td /var/folders/tr/ttb4qpdj3v12wvzr05hsdfv40000gn/T/openclaw-tg-api-dedup-*/live-api-dedup.ts | head -1); REPO_HEAD=$(git rev-parse --short=12 HEAD) NODE_PATH="$PWD/node_modules" pnpm exec tsx "$script" (seed 6036, block photo 6037, final text 6038, ok=true)
  • Changelog: CHANGELOG.md updated
  • Land commit: b22d4e7
  • Merge commit: 6bf9185

Thanks @rogerdigital!

Re-review progress:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram proof: supplied External PR includes structured after-fix real behavior proof. size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: MEDIA directive delivers attachments twice on Telegram (non-streaming)

2 participants