From c1c7260c8f38bcfa16d552a6aaad319b1cdbfe90 Mon Sep 17 00:00:00 2001 From: Alex Langenfeld Date: Thu, 13 Aug 2026 17:22:34 -0500 Subject: [PATCH 1/3] [core] Give events-consumer deferred-check tests a stall-proof budget The 'duplicate event classes' tests reach their outcome through the deferred check's multi-stage timer chain (promise queue -> setTimeout(0) -> idle poll -> delay timer), which a loaded CI runner with coarse timers can starve for whole seconds. The afterDeferredCheck poll capped that at 2s and two follow-up assertions used vi.waitFor's 1s default, inside the 5s default test timeout - regularly starved through on Windows runners ('does not track hook deliveries' and 'leaves a duplicate run_cancelled' flaked ~1.5x/day over the last 10 days, failing with strandedEvent/parkedSummary still undefined). The polls return as soon as their assertions hold, so raising the poll timeout to 15s and the suite budget to 30s costs healthy runs nothing while bounding only genuinely stalled runners. Signed-off-by: Alex Langenfeld --- packages/core/src/events-consumer.test.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/core/src/events-consumer.test.ts b/packages/core/src/events-consumer.test.ts index 7134d268bd..943fa2ef84 100644 --- a/packages/core/src/events-consumer.test.ts +++ b/packages/core/src/events-consumer.test.ts @@ -736,7 +736,12 @@ describe('EventsConsumer', () => { }); }); - describe('duplicate event classes', () => { + // The deferred check reaches its outcome through a multi-stage timer chain + // (promise queue → setTimeout(0) → idle poll → delay timer), and loaded CI + // runners with coarse timers — Windows especially — can starve that chain + // for whole seconds. The polls below return as soon as their assertions + // hold, so a generous test budget costs healthy runs nothing. + describe('duplicate event classes', { timeout: 30_000 }, () => { // Nothing here waits on the window for its result — a duplicate is stepped // over in the pass that offers it — so run at the shortest legal delay and // let the assertions that a check did NOT fire be cheap. @@ -761,8 +766,11 @@ describe('EventsConsumer', () => { // and the negatives alongside them are then evaluated at the moment the // check is known to have fired, which is what the assertions mean. function afterDeferredCheck(assertions: () => void): Promise { + // The timeout bounds a stalled runner, not the expected path: a healthy + // run satisfies the assertions within a few windows. 2s (the previous + // bound) was regularly starved through on Windows CI runners. return vi.waitFor(assertions, { - timeout: MIN_DEFERRED_CHECK_DELAY_MS * 200, + timeout: 15_000, interval: MIN_DEFERRED_CHECK_DELAY_MS, }); } @@ -961,7 +969,7 @@ describe('EventsConsumer', () => { expect(onDuplicateEvent).not.toHaveBeenCalled(); }); - await vi.waitFor(() => { + await afterDeferredCheck(() => { expect(onUnconsumedEvent).toHaveBeenCalledWith(events[3]); }); }); @@ -989,7 +997,7 @@ describe('EventsConsumer', () => { expect(onDuplicateEvent).not.toHaveBeenCalled(); }); - await vi.waitFor(() => { + await afterDeferredCheck(() => { expect(onUnconsumedEvent).toHaveBeenCalledWith(events[2]); }); }); From 25092841b26a9c00baee1686963fefb63951f13c Mon Sep 17 00:00:00 2001 From: Alex Langenfeld Date: Thu, 13 Aug 2026 17:23:04 -0500 Subject: [PATCH 2/3] [core] Match the TTL-expiration abort e2e test budget to its siblings The other distributedAbortController tests run with a 60s budget; the TTL-expiration one got 30s. Its 3s TTL is trivial, but on a fresh prod deployment cold starts plus queue backlog routinely push run start + first stream delivery past 30s - it timed out on two apps simultaneously in a single Tests run over the last week. Signed-off-by: Alex Langenfeld --- packages/core/e2e/e2e.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/e2e/e2e.test.ts b/packages/core/e2e/e2e.test.ts index 18b0650099..9600a99e6b 100644 --- a/packages/core/e2e/e2e.test.ts +++ b/packages/core/e2e/e2e.test.ts @@ -4188,7 +4188,11 @@ describe('e2e', () => { test( 'distributedAbortController - TTL expiration triggers signal', - { timeout: 30_000 }, + // Same budget as the sibling distributedAbortController tests: the 3s + // TTL is trivial, but cold starts plus queue backlog on a fresh prod + // deployment routinely pushed run start + stream delivery past the + // tighter 30s this test used to get. + { timeout: 60_000 }, async () => { const controllerId = `test-expire-${Math.random().toString(36).slice(2)}`; From e6b258e86a009e0b319064054760d2f10ac6091e Mon Sep 17 00:00:00 2001 From: Alex Langenfeld Date: Thu, 13 Aug 2026 17:25:07 -0500 Subject: [PATCH 3/3] Add changeset Signed-off-by: Alex Langenfeld --- .changeset/e2e-test-budgets.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/e2e-test-budgets.md diff --git a/.changeset/e2e-test-budgets.md b/.changeset/e2e-test-budgets.md new file mode 100644 index 0000000000..6b3f86684e --- /dev/null +++ b/.changeset/e2e-test-budgets.md @@ -0,0 +1,5 @@ +--- +'@workflow/core': patch +--- + +Fix flaky timing-sensitive tests: stall-proof budgets for the events-consumer deferred-check tests and a sibling-matched budget for the TTL-expiration abort e2e test.