The web unit test ServerImportJsonModal › guards against a live edit made before the debounce re-validates fails intermittently inside a full npm run local:gate, and passes every time in isolation.
Evidence
Observed across four gate runs on the same branch (#2245, whose diff touches zero files under clients/ — only .claude/, AGENTS.md, docs/ and scripts/ — so it cannot be the cause):
| gate run |
stage |
machine |
result |
| 1 |
— |
quiet |
clean, exit 0 |
| 2 |
coverage |
load 50–126 |
failed, 24 945 ms for the file |
| 3 |
coverage |
load ~150 |
a different test failed (ServerSettingsModal › SEP-2350), 90 787 ms |
| 4 |
validate |
quiet-ish (load ~18–40) |
failed again, 11 275 ms for the file / 1 567 ms for the test |
In isolation it is stable: 6/6 runs pass, ~11 s each.
npx vitest run --project=unit \
src/components/groups/ServerImportJsonModal/ServerImportJsonModal.test.tsx
Run 4 is the one that matters. I had initially attributed runs 2 and 3 to machine contention — another process had the box at load 100+ — but run 4 failed on a quiet machine, in the validate stage rather than coverage, with the test itself taking only 1.6 s. Contention alone does not explain that, so the honest read is a real timing race that parallelism makes more likely, not merely a slow machine.
Why it is worth fixing rather than tolerating
local:gate promises that a green local run means CI passes. A test that fails ~50% of full runs and 0% of isolated ones inverts that: it makes the gate a coin flip and trains people to re-run rather than read a red result — the same "channel nobody fails on" failure that Lint has no warning tier exists to prevent.
Where to look
clients/web/src/components/groups/ServerImportJsonModal/ServerImportJsonModal.test.tsx. The name says debounce, so the likely shapes are a real-timer wait racing a fake-timer advance, or an assertion that depends on a setTimeout landing before a React flush. ServerSettingsModal › maps the OAuth insufficient-scope policy into settings (SEP-2350) failed the same way once and may share a root cause; check both before assuming they are independent.
Acceptance
- The test passes deterministically inside a full
npm run local:gate, not only in isolation — demonstrated over several consecutive runs.
- The fix removes the race rather than widening a timeout, or says explicitly why a timeout is the right lever here.
The web unit test
ServerImportJsonModal› guards against a live edit made before the debounce re-validates fails intermittently inside a fullnpm run local:gate, and passes every time in isolation.Evidence
Observed across four gate runs on the same branch (#2245, whose diff touches zero files under
clients/— only.claude/,AGENTS.md,docs/andscripts/— so it cannot be the cause):coveragecoverageServerSettingsModal› SEP-2350), 90 787 msvalidateIn isolation it is stable: 6/6 runs pass, ~11 s each.
Run 4 is the one that matters. I had initially attributed runs 2 and 3 to machine contention — another process had the box at load 100+ — but run 4 failed on a quiet machine, in the
validatestage rather thancoverage, with the test itself taking only 1.6 s. Contention alone does not explain that, so the honest read is a real timing race that parallelism makes more likely, not merely a slow machine.Why it is worth fixing rather than tolerating
local:gatepromises that a green local run means CI passes. A test that fails ~50% of full runs and 0% of isolated ones inverts that: it makes the gate a coin flip and trains people to re-run rather than read a red result — the same "channel nobody fails on" failure that Lint has no warning tier exists to prevent.Where to look
clients/web/src/components/groups/ServerImportJsonModal/ServerImportJsonModal.test.tsx. The name says debounce, so the likely shapes are a real-timer wait racing a fake-timer advance, or an assertion that depends on asetTimeoutlanding before a React flush.ServerSettingsModal› maps the OAuth insufficient-scope policy into settings (SEP-2350) failed the same way once and may share a root cause; check both before assuming they are independent.Acceptance
npm run local:gate, not only in isolation — demonstrated over several consecutive runs.