fix(messaging): dedupe short-window getUpdates inbound replays - #240
ericcaiwx-star wants to merge 3 commits into
Conversation
Claim message_id/client_id/seq (or body fingerprint) before side effects so at-least-once iLink long-polls do not run the AI pipeline twice. Derive MessageSid from the same stable key when present. Fixes Tencent#239 Co-authored-by: Cursor <cursoragent@cursor.com>
Spell out that the 5-minute TTL is for getUpdates replays (not swallowing intentional user re-sends), and that the in-memory claim is process-local / single-instance only. Co-authored-by: Cursor <cursoragent@cursor.com>
Upgrade the getUpdates replay guard from a process-local Map to OpenClaw createClaimableDedupe with a 24h disk tombstone under OPENCLAW_STATE_DIR, covering ~1s iLink doubles and longer redeliveries after stuck long turns without swallowing intentional re-sends. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks for putting together a thorough replay-deduplication patch. The same at-least-once delivery gap exists in the current community release, and we would prefer an author-led port so the implementation context and authorship stay with you. If you are interested, we would be glad to review a focused PR against: https://github.com/NewFuture/openclaw-weixin For the current codebase, please adapt the claim/commit flow to the ordinary and approval admission lanes, confirm compatibility with the supported OpenClaw persistent-dedupe API, and retain account-scoped storage. Regression coverage for restart persistence, cross-account isolation, claim release after failure/abort, media, and slash-command delivery would be especially helpful. Contribution guide: For transparency, |
|
Community port opened as invited: NewFuture/openclaw-weixin#41 Adapted for ordinary/approval admission lanes and OpenClaw 2026.7.1 |
…nes (#41) * fix(messaging): persist getUpdates replay claims on both admission lanes Claim/commit OpenClaw createClaimableDedupe tombstones (pluginId openclaw-weixin, account namespace, 24h) inside ordinary and approval lanes so at-least-once iLink replays do not run twice across restart. Stable MessageSid follows the same transport key when present. Port of Tencent/openclaw-weixin#240 for the community distribution. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(messaging): keep replay dedupe compatible with OpenClaw 2026.6.1 Use the resolveFilePath createClaimableDedupe shape so min-host CI can typecheck, and soften Windows temp-dir cleanup when the state DB stays open. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(messaging): reclaim after in-flight release and redact dedupe logs Wait on ClaimableDedupe inflight pending so a replay is dropped only after the owner commits; reclaim if the owner releases. Duplicate logs keep only non-sensitive identity kinds (mid/cid/seq/body). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(messaging): keep lanes free during inflight replay waits Observe ClaimableDedupe inflight pending outside ordinary/approval lanes and re-enqueue only after owner release; wrap all post-admission work in the claim lifecycle; prefer item msg_id digests and never key by sender alone for empty-body media. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
message_id→client_id→seq→ body fingerprint) at the top ofprocessOneMessagevia OpenClawcreateClaimableDedupe, then commit a disk tombstone under$OPENCLAW_STATE_DIR/openclaw-weixin/replay-dedupe/so at-least-oncegetUpdatesdeliveries do not run the AI pipeline twice.MessageSidfrom the same key when transport ids are present.Fixes #239
Related: openclaw/openclaw#116723
Scope / deploy assumption
message_idis processed.OPENCLAW_STATE_DIR). Multi-replica gateways without shared state still need a shared store or full channel ingress drain — out of scope here; this matches the transitionalcreateClaimableDedupepattern used by other channels (Discord / Mattermost / Nextcloud Talk / Feishu).message_idon the wireiLink
GetUpdatesResp.msgs[]items areWeixinMessage; README documentsmessage_idas the unique message id. Production debug already logsmsgId/seq.{ "seq": 1234567890, "message_id": 9876543210, "from_user_id": "<redacted>", "to_user_id": "<redacted>", "create_time_ms": 1720000000000, "item_list": [{ "type": 1, "text_item": { "text": "<redacted>" } }], "context_token": "<redacted>" }Test plan
npx vitest run src/messaging/inbound-dedupe.test.ts src/messaging/inbound.test.tsnpx tsc --noEmitmessage_idwithin 24h is dropped; intentional re-send (new id) still processes~/.openclaw/openclaw-weixin/replay-dedupe/still suppresses replay