fix(telegram): deduplicate MEDIA attachments in non-streaming mode#78420
Conversation
|
Codex review: needs maintainer review before merge. Summary 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 Next step before merge Security Review detailsBest 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:
What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 337148a82100. Re-review progress:
|
|
CI failure note: The 3 failing checks (
|
|
Real behavior proof — Telegram non-streaming media dedup Branch: Test: Asked bot to search for a cat image and send it. |
925b5d7 to
7869192
Compare
369f43c to
741034b
Compare
741034b to
e3855dc
Compare
|
CI failure note (updated): Branch-specific Telegram tests pass locally: 45/45 in |
|
Addressed the P2 finding (track block media only after successful delivery):
Commit: 2c08700 All 108 CI checks pass, conflict resolved, ready for review. @steipete — would appreciate a look when you have a moment. |
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.
|
Landed via rebase onto main.
Thanks @rogerdigital! Re-review progress:
|

Summary
MEDIA:attachment twice — once from the media-only block reply and once from the final replySet, then filter duplicates from final reply payloadsdeduplicateBlockSentMediainto a standalone pure function for testabilitymediaUrlfallback when allmediaUrlsare deduped, preventingresolvePayloadMediaUrlsfrom resending via the single-URL pathCloses #78372
Root cause
When
streaming: "off", thedelivercallback inbot-message-dispatch.tsreceives both:mediaUrlspopulated (sent directly viasendDirectBlockReplyinreply-delivery.ts:160, which sends media-only blocks even when streaming is off)mediaUrls(theMEDIA:directive text persists in the agent's complete output)Unlike WebChat (which has
appendedWebchatAgentMediaguard atchat.ts:2512) or the streaming path (which deduplicates viablockReplyPipeline), Telegram's non-streamingdelivercallback had no mechanism to detect that the same media was already delivered.Changes
bot-message-dispatch.ts: AddsentBlockMediaUrlstracking set insiderunDispatch. In thedelivercallback, record block-reply media URLs and calldeduplicateBlockSentMediafor final replies. All downstream usage switches frompayloadtoeffectivePayload.bot-message-dispatch.media-dedup.ts(new): Pure functiondeduplicateBlockSentMedia— returns deduplicated payload, orundefinedto skip entirely. Also clears legacymediaUrlwhen allmediaUrlsentries 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 legacymediaUrlclearing.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:
pnpm buildopenclaw gateway restartEvidence after fix:
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
blockReplyPipelinededup path, not affected by this change). Multi-image payloads in live environment (covered by unit tests).Test plan
pnpm test extensions/telegram/src/bot-message-dispatch.media-dedup.test.ts— 9/9pnpm test extensions/telegram/src/bot-message-dispatch.test.ts— 45/45pnpm exec oxfmt --checkon all changed filespnpm test extensions/telegram