From df27091f5f690a6b83722ab14d6026966f121859 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 13 Apr 2026 16:36:03 +0100 Subject: [PATCH] fix(auto-reply): avoid leaking inbound debounce cleanup --- src/auto-reply/inbound-debounce.ts | 5 +++-- src/auto-reply/inbound.test.ts | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/auto-reply/inbound-debounce.ts b/src/auto-reply/inbound-debounce.ts index 194dee6f0c2..078e9567e3e 100644 --- a/src/auto-reply/inbound-debounce.ts +++ b/src/auto-reply/inbound-debounce.ts @@ -86,11 +86,12 @@ export function createInboundDebouncer(params: InboundDebounceCreateParams const next = previous.catch(() => undefined).then(task); const settled = next.catch(() => undefined); keyChains.set(key, settled); - void settled.finally(() => { + const cleanup = () => { if (keyChains.get(key) === settled) { keyChains.delete(key); } - }); + }; + settled.then(cleanup, cleanup); return next; }; diff --git a/src/auto-reply/inbound.test.ts b/src/auto-reply/inbound.test.ts index 6a47db79161..d8bd1d76be9 100644 --- a/src/auto-reply/inbound.test.ts +++ b/src/auto-reply/inbound.test.ts @@ -521,6 +521,29 @@ describe("createInboundDebouncer", () => { expect(calls).toEqual(["1", "2"]); }); + it("does not leak unhandled rejections when a keyed flush failure is awaited", async () => { + const debouncer = createInboundDebouncer<{ key: string; id: string }>({ + debounceMs: 0, + buildKey: (item) => item.key, + onFlush: async () => { + throw new Error("flush failed"); + }, + }); + const unhandled: unknown[] = []; + const onUnhandledRejection = (reason: unknown) => { + unhandled.push(reason); + }; + process.on("unhandledRejection", onUnhandledRejection); + + try { + await expect(debouncer.enqueue({ key: "a", id: "1" })).resolves.toBeUndefined(); + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(unhandled).toEqual([]); + } finally { + process.off("unhandledRejection", onUnhandledRejection); + } + }); + it("bypasses debouncing for new keys once the tracked-key cap is reached", async () => { vi.useFakeTimers(); const calls: Array = [];