Skip to content

Commit 7ad0804

Browse files
Brooooooklynclaude
andauthored
Stop counting macOS setTypeOfService EINVAL as a test failure (#389)
## Problem Full-suite runs on macOS sometimes end with one unhandled error, `setTypeOfService EINVAL`, blamed on `node-target.test.ts`. All tests pass, but Vitest counts the error, so the run fails. ``` test worker (Node 24, macOS) vp dev (Vite) ---------------------------- ------------- waitForServer: fetch 127.0.0.1:P ---> port check: listen 0.0.0.0:P, then close handshake done, still queued close() resets the queued connection (RST) 'connect' callback runs undici 7: socket.setTypeOfService(0) setsockopt(IP_TOS) -> EINVAL throws inside the event handler -> uncaught -> Vitest "1 error" -> run fails (the fetch itself rejects with ECONNRESET and waitForServer retries) ``` - Node 24 bundles undici 7. On each new connection it calls `socket.setTypeOfService()` with no `try/catch`. - On macOS, `setsockopt(IP_TOS)` returns EINVAL once the peer has reset the socket. Linux returns 0, and Node ignores the error on Windows. - Vite checks that the port is free by briefly listening on `0.0.0.0` and `::`. If the test polls during that moment, its connection gets reset. - Upstream: nodejs/undici#5544. It is fixed in undici 8.8+, but undici 7 (up to 7.30.0, used by Node 24) still has the bug. ## Fix Add a root `test.onUnhandledError` filter that drops only errors with `syscall: 'setTypeOfService'` and `code: 'EINVAL'`. This does the same thing as the upstream fix: it treats the failed call as best-effort. The fetch still rejects, so callers see the failure as before. Other unhandled errors are still reported. We can remove the filter once Node 24 ships an undici with the fix. ## Testing | Check | Before | After | | --- | --- | --- | | Test that forces a reset (10 runs) | 10/10 runs fail | 0/10 fail, 12 errors dropped | | `node-target.test.ts` with fast polling to widen the race (25 runs) | 6/25 fail with this exact error | 0/25 fail, 4 errors dropped | | `node-target.test.ts` as-is: 20 runs idle, 40 under CPU load | 0/60 (race is rare at 200 ms polling) | - | | Real `vp dev` startup with tight fetch polling (30 starts) | 7 uncaught EINVAL | - | | Control: built Node server, same polling (30 starts) | 0 | - | | Unrelated uncaught error | reported | still reported | | Linux (Docker, Node 24.21.0), reset socket | `setsockopt` returns 0 | - | | `vp lint`, `vp fmt --check`, `node-target.test.ts` | - | pass | | `vp test run` (full suite, macOS) | - | pass: 688 files passed, 1 skipped; 13062 tests passed, 29 skipped | 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent a76befe commit 7ad0804

1 file changed

Lines changed: 9 additions & 0 deletions

File tree

‎vite.config.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,15 @@ export default defineConfig({
399399
fsModuleCache: true,
400400
silent: 'passed-only',
401401
sequence: { sequencer: SuiteSequencer },
402+
// Node 24 bundles undici 7. Its fetch() calls socket.setTypeOfService() on each new
403+
// connection and does not catch errors. On macOS this throws EINVAL when the server has
404+
// already reset the connection, e.g. when a test polls a port while Vite briefly binds it
405+
// to check that it is free. The fetch still rejects and the caller sees that; only this
406+
// stray uncaught copy is dropped. undici 8.8+ ignores the error itself.
407+
onUnhandledError(error) {
408+
const { code, syscall } = error as { code?: unknown; syscall?: unknown };
409+
return !(syscall === 'setTypeOfService' && code === 'EINVAL');
410+
},
402411
projects: [
403412
{
404413
extends: false,

0 commit comments

Comments
 (0)