Skip to content

Commit 42e6b3c

Browse files
committed
fix(feishu): add early event-level dedup to prevent duplicate message processing
Feishu webhook retries and WebSocket reconnect replays can deliver the same event multiple times. The existing messageId dedup in handleFeishuMessage runs downstream after the debouncer, so two concurrent dispatches of the same event can both enter the pipeline before either records the messageId. Add a synchronous in-memory dedup check at the EventDispatcher handler level using message_id as key with a 5-minute TTL. This catches duplicates immediately when they arrive, before they enter the inbound debouncer or processing queue. The downstream persistent dedup in bot.ts is preserved for cross-restart protection. Closes #37477
1 parent ee6f7b1 commit 42e6b3c

File tree

1 file changed

+16
-1
lines changed

1 file changed

+16
-1
lines changed

extensions/feishu/src/monitor.account.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as crypto from "crypto";
22
import * as Lark from "@larksuiteoapi/node-sdk";
33
import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "openclaw/plugin-sdk/feishu";
4+
import { createDedupeCache } from "openclaw/plugin-sdk/feishu";
45
import { resolveFeishuAccount } from "./accounts.js";
56
import { raceWithTimeoutAndAbort } from "./async.js";
67
import {
@@ -376,10 +377,24 @@ function registerEventHandlers(
376377
},
377378
});
378379

380+
// Early event-level dedup to drop duplicate webhook retries and WebSocket replays
381+
// before they enter the debouncer or processing pipeline. The downstream dedup in
382+
// handleFeishuMessage guards against restarts (persistent), but cannot prevent two
383+
// concurrent dispatches of the same event from both being enqueued.
384+
const eventDedup = createDedupeCache({ ttlMs: 5 * 60 * 1000, maxSize: 2_000 });
385+
379386
eventDispatcher.register({
380387
"im.message.receive_v1": async (data) => {
388+
const event = data as unknown as FeishuMessageEvent;
389+
const messageId = event.message?.message_id?.trim();
390+
if (messageId) {
391+
const eventKey = `${accountId}:evt:${messageId}`;
392+
if (!eventDedup.check(eventKey)) {
393+
log(`feishu[${accountId}]: dropping duplicate event for message ${messageId}`);
394+
return;
395+
}
396+
}
381397
const processMessage = async () => {
382-
const event = data as unknown as FeishuMessageEvent;
383398
await inboundDebouncer.enqueue(event);
384399
};
385400
if (fireAndForget) {

0 commit comments

Comments
 (0)