Skip to content

fix(send-media): dedup sendWeixinMediaFile within 5s window (#74)#90

Open
draix wants to merge 1 commit into
Tencent:mainfrom
draix:fix/74-send-media-file-dedup
Open

fix(send-media): dedup sendWeixinMediaFile within 5s window (#74)#90
draix wants to merge 1 commit into
Tencent:mainfrom
draix:fix/74-send-media-file-dedup

Conversation

@draix
Copy link
Copy Markdown
Contributor

@draix draix commented Apr 27, 2026

Summary

Fixes #74sendWeixinMediaFile was being dispatched twice for the same (to, filePath) pair within ~4 seconds, so recipients received the same image / file twice.

This PR adds a plugin-side 5 second in-memory dedup keyed on ${to}::${filePath}. A second invocation landing inside the window short-circuits and returns the previous messageId without re-uploading.

Root cause (upstream)

The OpenClaw core delivery layer re-parses MEDIA: directives out of agent text payloads. When an agent's message tool call carries both surrounding text and an explicit media field, the explicit and the parsed paths both reach the plugin's sendWeixinMediaFile, producing two uploads with two distinct filekey values.

The proper fix lives in core (see openclaw/openclaw#61535 for the mirror condition where the text portion is dropped instead). This PR is the plugin-side defense — the plugin is the last line of defense and can't trust upstream not to call it twice.

Behavior

Scenario Before After
One message call with text + media 2 uploads, 2 sends — recipient sees duplicate 1 upload, 1 send — second invocation deduped
Two distinct media paths to same recipient 2 sends 2 sends (unchanged)
Same media path to two recipients 2 sends 2 sends (unchanged)
Same (to, filePath) more than 5s apart 2 sends 2 sends (unchanged)

Reproduction (from the issue)

A single message tool call:

{"action":"send","message":"喏 👇","target":"...@im.wechat","media":"/home/node/.openclaw/media/tool-image-generation/image-1---75cc94db.jpg"}

Produces two distinct uploads in the log:

17:55:53 sendWeixinMediaFile: uploading image filePath=...75cc94db.jpg to=...@im.wechat
17:56:08 image upload done filekey=b7baffa7433e0bd07ce19cd0309222a1 size=624156

17:56:12 sendWeixinMediaFile: uploading image filePath=...75cc94db.jpg to=...@im.wechat   ← SAME FILE
17:56:23 image upload done filekey=2d8cafac78f83369255a2b5886003454 size=624156           ← NEW FILEKEY

With this PR, the second sendWeixinMediaFile (≤5 s later, same (to, filePath)) is short-circuited:

17:56:12 sendWeixinMediaFile: DEDUP — skipping duplicate send to=...@im.wechat filePath=...75cc94db.jpg messageId=...

Implementation

  • Module-level Map<string, { ts: number; messageId: string }> keyed by ${to}::${filePath}.
  • DEDUP_WINDOW_MS = 5000.
  • Bounded: once the map exceeds 100 entries, anything older than 60 s is evicted.
  • Test-only __resetSendMediaDedupForTests and __sendMediaDedupSizeForTests exports for assertions; production code does not depend on them.

Tests

src/messaging/send-media.test.ts adds 6 new tests (now 12 total in this file). All pass; typecheck is clean.

  • second call within window short-circuits and returns the previous messageId
  • calls outside the window both go through (fake timers, advance past 5 s)
  • different filePath values to the same recipient both go through
  • same filePath to different recipients both go through
  • GC drops stale entries once the threshold is exceeded
  • dedup also covers the file-attachment branch (not only image)
✓ src/messaging/send-media.test.ts (12 tests) 6ms

The unrelated pairing.test.ts > uses withFileLock failure exists on main already — it's not introduced by this PR.


cc/ maintainers — happy to iterate on logging level, window size, or convert the warn to a counter/metric if you have telemetry preferences.

When the OpenClaw core delivery layer re-parses a MEDIA: directive from an
agent's text payload AND simultaneously honors an explicit `media` field
passed to the message tool, both code paths reach this plugin. The result
is two sendWeixinMediaFile calls for the same (to, filePath) pair within a
few seconds — recipients see duplicate images / files (and the second
upload uses a fresh filekey, so it's not a CDN cache hit).

Until the upstream is fixed, add a plugin-side dedup keyed on
`${to}::${filePath}` with a 5 second window. Repeat invocations
short-circuit and return the previous messageId without re-uploading. The
map is bounded: once it grows past 100 entries we drop anything older than
60 seconds.

Tests cover: same-key within/outside window, cross-key independence
(different recipients or filePaths), file-attachment branch, and GC
bounding.

Refs: openclaw/openclaw#61535 — the dual condition (text dropped) tracks
the same upstream bug from the other direction.

Fixes Tencent#74
Copy link
Copy Markdown

@Re-Ch-X Re-Ch-X left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个PR有一些问题,详见 #74 (comment)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] sendWeixinMediaFile dispatched twice for same (to, filePath) within seconds — duplicate image sent to recipient

3 participants