From 143939dd2e98388c4e52f213083a6075279e1d5d Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 21 Aug 2026 02:58:14 -0700 Subject: [PATCH] Wait for the Slack reply instead of assuming the microtask drained The reply lands on a later microtask than the dispatch resolves, so the assertion passed locally and failed on CI. --- packages/slack-tag/src/dispatch.test.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/slack-tag/src/dispatch.test.ts b/packages/slack-tag/src/dispatch.test.ts index 86f10b95a..3c9f5982e 100644 --- a/packages/slack-tag/src/dispatch.test.ts +++ b/packages/slack-tag/src/dispatch.test.ts @@ -120,6 +120,24 @@ function fakeEvent(overrides: Partial = {}): TagEvent { }; } +/** + * Waits for a condition the dispatch resolves before, not with — the reply + * arrives on a later microtask, and how many turns of the loop that takes + * is an implementation detail no assertion should encode. + */ +async function waitFor( + condition: () => boolean, + timeoutMs = 1000, +): Promise { + const deadline = Date.now() + timeoutMs; + while (!condition()) { + if (Date.now() > deadline) { + throw new Error("timed out waiting for the condition"); + } + await new Promise((resolve) => setTimeout(resolve, 1)); + } +} + function fakeThread(): TagThread & { posts: string[]; subscribed: boolean } { const posts: string[] = []; return { @@ -287,6 +305,10 @@ describe("dispatchWorkbenchSlackEvent", () => { const thread = fakeThread(); await dispatchWorkbenchSlackEvent(deps, fakeEvent(), thread); + // The reply lands on a later microtask than the dispatch resolves, so + // wait for the post itself rather than assuming this turn of the loop + // already drained it. + await waitFor(() => thread.posts.length > 0); expect(thread.posts).toEqual(["Hi! How can I help?"]); });