Observed once during a full LC_ALL=C make ts-check run while verifying #781. Unrelated to that PR — see "Why it is not the change under test" below — but it is a latent red that will flake in CI on somebody else's branch.
The failure
typescript/tests/middleware-lifecycle.test.ts:269 — "rejects promptly when the caller aborts during a retry backoff".
FAIL tests/middleware-lifecycle.test.ts > middleware request lifecycle
> rejects promptly when the caller aborts during a retry backoff
AssertionError: expected undefined to be Error: caller cancelled during backoff
- Expected: Error { "message": "caller cancelled during backoff" }
+ Received: undefined
❯ tests/middleware-lifecycle.test.ts:297:19
undefined is the resolve branch of .then(() => undefined, (e) => e), so the request completed instead of rejecting: the 100 ms abort timer did not land inside the backoff window it is aiming at.
Cleared 3/3 on the single test in isolation, 2/2 on the whole file, and the full suite passed on an immediate re-run (87 files / 1654 tests). So it is timing-sensitive, not broken — which, as #655 put it, is the worst kind of red: it fails rarely, on an unrelated PR, and costs someone an hour deciding whether they caused it.
This is #655's shape, and #655's survey missed it
#655 fixed exactly this race in client.test.ts — a short abort timer against a longer mocked response, plus a redundant wall-clock assertion — and scoped itself with:
It is the only test in typescript/tests/ with this shape (rg "setTimeout\(\(\) => controller.abort\(\), [0-9]+\)" returns one hit), so the fix is contained.
That was already false when it was written. The selector requires a no-argument abort(), and both surviving instances pass a reason:
$ rg -n 'setTimeout\(\(\) => controller\.abort\(\), [0-9]+\)' typescript/tests | wc -l
0
$ rg -n 'setTimeout\(\(\) => controller\.abort\([^)]*\), [0-9]+\)' typescript/tests
typescript/tests/middleware-lifecycle.test.ts:244: const abortTimer = setTimeout(() => controller.abort(reason), 50);
typescript/tests/middleware-lifecycle.test.ts:286: const abortTimer = setTimeout(() => controller.abort(reason), 100);
Both landed in #538 (4ef2c909f, 2026-07-31), a fortnight before #655 was filed. One regex character class is the whole reason they were not in scope.
Line 244 is not merely the same species, it is the same race: a 50 ms abort timer against a 1000 ms mocked response, which is #655's own numbers exactly. It has not been seen red yet; it is one scheduling hiccup from it.
Line 286 (the one that failed) is a variant: it races the 100 ms timer against a 2 s Retry-After backoff, and pairs it with expect(Date.now() - startedAt).toBeLessThan(1000) — the same redundant wall-clock assertion #655 identified as contributing all of the load sensitivity and none of the discriminating power.
So this is not "a third flaky test to widen". It is the second and third instances of one shape whose remedy is already written down.
The remedy is #655's option 1, and there is a worked example in-tree
Abort deterministically instead of on a timer. Call controller.abort() after the request is in flight … so no wall-clock margin is involved at all.
#781 needed the same thing for a different reason — asserting a three-minute server-directed delay without waiting it out — and used exactly that idiom: it aborts from inside the retrying lifecycle hook, which the loop fires immediately before it sleeps.
retrying: (_failedAttempt, _error, delayMs) => {
chosen = delayMs;
controller.abort(new Error("delay captured"));
},
That is deterministic — no margin, no load sensitivity — and it settles in ~1 ms rather than 100. typescript/tests/retry-after.test.ts is the worked example; both tests here have the same hooks available.
Sketch for line 286: abort from hooks.onRetry instead of a timer, keep expect(err).toBe(reason) and the start/end attempt assertions, and drop toBeLessThan(1000) — err === reason already proves the caller's signal won, which is the property under test. Line 244 wants the same treatment against onRequestStart.
Why it is not the change under test
#781 changes parseRetryAfter. This test sends Retry-After: 2, which the new parser handles identically to the old one: ^[+-]?\d+$ matches, Number("2") is 2, > 0, returned — same value, same code path, no new work. The test also passed 3/3 in isolation and 2/2 for the whole file against the changed parser, and the full suite was green on re-run.
What #781 plausibly did do is expose it. It added ~3 s of real sleeps to two sibling files (account-logo.test.ts and retry-after.test.ts, which now assert genuine backoff intervals where a defect previously collapsed them to zero), which shifts vitest's worker scheduling and changes what else is competing for the event loop when this file runs. That is worth recording for whoever picks this up: the trigger is load and interleaving, so the reproduction is a full-suite run, not this file.
Deliberately not fixed in #781 — it is someone else's test and hardening it there would widen a parsing fix into a de-flaking one.
Observed once during a full
LC_ALL=C make ts-checkrun while verifying #781. Unrelated to that PR — see "Why it is not the change under test" below — but it is a latent red that will flake in CI on somebody else's branch.The failure
typescript/tests/middleware-lifecycle.test.ts:269— "rejects promptly when the caller aborts during a retry backoff".undefinedis the resolve branch of.then(() => undefined, (e) => e), so the request completed instead of rejecting: the 100 ms abort timer did not land inside the backoff window it is aiming at.Cleared 3/3 on the single test in isolation, 2/2 on the whole file, and the full suite passed on an immediate re-run (87 files / 1654 tests). So it is timing-sensitive, not broken — which, as #655 put it, is the worst kind of red: it fails rarely, on an unrelated PR, and costs someone an hour deciding whether they caused it.
This is #655's shape, and #655's survey missed it
#655 fixed exactly this race in
client.test.ts— a short abort timer against a longer mocked response, plus a redundant wall-clock assertion — and scoped itself with:That was already false when it was written. The selector requires a no-argument
abort(), and both surviving instances pass a reason:Both landed in #538 (
4ef2c909f, 2026-07-31), a fortnight before #655 was filed. One regex character class is the whole reason they were not in scope.Line 244 is not merely the same species, it is the same race: a 50 ms abort timer against a 1000 ms mocked response, which is #655's own numbers exactly. It has not been seen red yet; it is one scheduling hiccup from it.
Line 286 (the one that failed) is a variant: it races the 100 ms timer against a 2 s
Retry-Afterbackoff, and pairs it withexpect(Date.now() - startedAt).toBeLessThan(1000)— the same redundant wall-clock assertion #655 identified as contributing all of the load sensitivity and none of the discriminating power.So this is not "a third flaky test to widen". It is the second and third instances of one shape whose remedy is already written down.
The remedy is #655's option 1, and there is a worked example in-tree
#781 needed the same thing for a different reason — asserting a three-minute server-directed delay without waiting it out — and used exactly that idiom: it aborts from inside the
retryinglifecycle hook, which the loop fires immediately before it sleeps.That is deterministic — no margin, no load sensitivity — and it settles in ~1 ms rather than 100.
typescript/tests/retry-after.test.tsis the worked example; both tests here have the same hooks available.Sketch for line 286: abort from
hooks.onRetryinstead of a timer, keepexpect(err).toBe(reason)and the start/end attempt assertions, and droptoBeLessThan(1000)—err === reasonalready proves the caller's signal won, which is the property under test. Line 244 wants the same treatment againstonRequestStart.Why it is not the change under test
#781 changes
parseRetryAfter. This test sendsRetry-After: 2, which the new parser handles identically to the old one:^[+-]?\d+$matches,Number("2")is 2,> 0, returned — same value, same code path, no new work. The test also passed 3/3 in isolation and 2/2 for the whole file against the changed parser, and the full suite was green on re-run.What #781 plausibly did do is expose it. It added ~3 s of real sleeps to two sibling files (
account-logo.test.tsandretry-after.test.ts, which now assert genuine backoff intervals where a defect previously collapsed them to zero), which shifts vitest's worker scheduling and changes what else is competing for the event loop when this file runs. That is worth recording for whoever picks this up: the trigger is load and interleaving, so the reproduction is a full-suite run, not this file.Deliberately not fixed in #781 — it is someone else's test and hardening it there would widen a parsing fix into a de-flaking one.