fix(feishu): add early event-level dedup to prevent duplicate replies

Add synchronous in-memory dedup at EventDispatcher handler level using
message_id as key with 5-minute TTL and 2000-entry cap.

This catches duplicate events immediately when they arrive from the Lark
SDK — before the inbound debouncer or processing queue — preventing the
race condition where two concurrent dispatches enter the pipeline before
either records the messageId in the downstream dedup layer.

Fixes the root cause reported in #42687.
This commit is contained in:
yunweibang
2026-03-12 14:38:37 +08:00
committed by Tak Hoffman
parent 98716bc0d7
commit a8ae682bda

View File

@@ -1,6 +1,7 @@
import * as crypto from "crypto";
import * as Lark from "@larksuiteoapi/node-sdk";
import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "openclaw/plugin-sdk/feishu";
import { createDedupeCache } from "openclaw/plugin-sdk/feishu";
import { resolveFeishuAccount } from "./accounts.js";
import { raceWithTimeoutAndAbort } from "./async.js";
import {
@@ -389,10 +390,24 @@ function registerEventHandlers(
},
});
// Early event-level dedup to drop duplicate webhook retries and WebSocket replays
// before they enter the debouncer or processing pipeline. The downstream dedup in
// handleFeishuMessage guards against restarts (persistent), but cannot prevent two
// concurrent dispatches of the same event from both being enqueued.
const eventDedup = createDedupeCache({ ttlMs: 5 * 60 * 1000, maxSize: 2_000 });
eventDispatcher.register({
"im.message.receive_v1": async (data) => {
const event = data as unknown as FeishuMessageEvent;
const messageId = event.message?.message_id?.trim();
if (messageId) {
const eventKey = `${accountId}:evt:${messageId}`;
if (!eventDedup.check(eventKey)) {
log(`feishu[${accountId}]: dropping duplicate event for message ${messageId}`);
return;
}
}
const processMessage = async () => {
const event = data as unknown as FeishuMessageEvent;
await inboundDebouncer.enqueue(event);
};
if (fireAndForget) {