You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follows #190, which fixed three state leaks and took the flake rate from roughly
15% to one failure in about 57 runs. This is the residual, which looks like a
different mechanism.
The observation
The husky pre-commit hook runs npm run coverage. On one commit it failed:
❯ tests/integration/internal/internal.spec.ts (25 tests | 1 failed) 20122ms
× returns 500 when the anomaly query fails 20010ms
FAIL tests/integration/internal/internal.spec.ts
> GET /internal/security/anomalies (additional branches)
> returns 500 when the anomaly query fails
Error: Test timed out in 20000ms.
Not reproduced in 16 subsequent coverage runs (8 parallel, 8 sequential), nor in
40 runs of vitest run (20 of each). So it is rare, and it only has been seen
under coverage.
The test is a single request against a handler that cannot hang on its own logic:
it('returns 500 when the anomaly query fails',async()=>{(AuthEvent.findAllasany).mockRejectedValue(newError('boom'));constres=awaitrequest(app).get('/internal/security/anomalies');expect(res.status).toBe(500);});
getSecurityAnomalies in src/controllers/internalSecurity.ts is one AuthEvent.findAll inside a try, with a catch that returns 500 {"error":"Failed to detect anomalies"}. The route is auth: 'access' plus requireAdmin('read'); the spec mocks attachAuthMiddleware locally and requireAdmin is mocked globally to call next(), so neither middleware can
swallow the request. There is no await in that path that can fail to settle.
A 20 second wait for that is not an ordering problem, which is what #190 was. It
looks more like the worker being starved: under --coverage the suite takes
~34s sequential against ~2s for vitest run, all 93 files share one worker under --fileParallelism=false, and v8 coverage instrumentation is on top of that.
Worth checking
Whether it only happens under --fileParallelism=false --coverage, which is
what the pre-commit hook used at the time. The hook now runs the parallel
coverage command, so reproducing may need the old flag explicitly.
Whether an unhandled rejection is involved. mockRejectedValue sets the
default implementation, so any straggler call to AuthEvent.findAll from an
earlier test's fire-and-forget path would also reject, and an unhandled
rejection can take the worker with it.
Whether the 20s testTimeout is simply too tight for the slowest coverage runs
on a loaded machine, in which case this is a threshold to raise rather than a
bug. Worth measuring the p99 duration of the integration specs under coverage
before assuming it is a hang.
tests/integration/internal/internal.spec.ts is the second heaviest mockResolvedValueOnce user in the suite (14 of them), behind organizations.spec.ts (22). The one pre-Test suite is intermittently flaky, including in the sequential coverage run #190 timeout observed was in organizations.spec.ts. That may be a coincidence now that the queues are
drained between tests, or it may mean something is still off in those two files
specifically.
Why it matters
The hook runs the full coverage suite on every commit, so a rare flake still
blocks a commit that is fine, and the fix is to retry. That is the habit #190
was about breaking.
Acceptance
The cause is identified, or the timeout is shown to be a threshold problem and
adjusted with the measurement that justifies it
Follows #190, which fixed three state leaks and took the flake rate from roughly
15% to one failure in about 57 runs. This is the residual, which looks like a
different mechanism.
The observation
The husky pre-commit hook runs
npm run coverage. On one commit it failed:Not reproduced in 16 subsequent coverage runs (8 parallel, 8 sequential), nor in
40 runs of
vitest run(20 of each). So it is rare, and it only has been seenunder coverage.
Why it is probably not the #190 mechanism
The test is a single request against a handler that cannot hang on its own logic:
getSecurityAnomaliesinsrc/controllers/internalSecurity.tsis oneAuthEvent.findAllinside atry, with acatchthat returns500 {"error":"Failed to detect anomalies"}. The route isauth: 'access'plusrequireAdmin('read'); the spec mocksattachAuthMiddlewarelocally andrequireAdminis mocked globally to callnext(), so neither middleware canswallow the request. There is no
awaitin that path that can fail to settle.A 20 second wait for that is not an ordering problem, which is what #190 was. It
looks more like the worker being starved: under
--coveragethe suite takes~34s sequential against ~2s for
vitest run, all 93 files share one worker under--fileParallelism=false, and v8 coverage instrumentation is on top of that.Worth checking
--fileParallelism=false --coverage, which iswhat the pre-commit hook used at the time. The hook now runs the parallel
coverage command, so reproducing may need the old flag explicitly.
mockRejectedValuesets thedefault implementation, so any straggler call to
AuthEvent.findAllfrom anearlier test's fire-and-forget path would also reject, and an unhandled
rejection can take the worker with it.
testTimeoutis simply too tight for the slowest coverage runson a loaded machine, in which case this is a threshold to raise rather than a
bug. Worth measuring the p99 duration of the integration specs under coverage
before assuming it is a hang.
tests/integration/internal/internal.spec.tsis the second heaviestmockResolvedValueOnceuser in the suite (14 of them), behindorganizations.spec.ts(22). The one pre-Test suite is intermittently flaky, including in the sequential coverage run #190 timeout observed was inorganizations.spec.ts. That may be a coincidence now that the queues aredrained between tests, or it may mean something is still off in those two files
specifically.
Why it matters
The hook runs the full coverage suite on every commit, so a rare flake still
blocks a commit that is fine, and the fix is to retry. That is the habit #190
was about breaking.
Acceptance
adjusted with the measurement that justifies it
npm run coverageruns pass