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
{{ message }}
Repository navigation
writeH1 calls socket.setTypeOfService() unconditionally — uncaught EINVAL on macOS crashes the process #5544
writeH1 calls socket.setTypeOfService(request.typeOfService) unconditionally on every HTTP/1.1 request, with no error handling. Since Request defaults typeOfService to 0 (this.typeOfService = typeOfService ?? 0 in lib/core/request.js), the setsockopt path runs even when the user never asked for any QoS marking. On macOS, Socket.setTypeOfService can throw EINVAL synchronously depending on socket state — and because the call site has no try/catch, the exception is uncaught and kills the whole process.
node:net:829
throw new ErrnoException(err, 'setTypeOfService');
^
Error: setTypeOfService EINVAL
at Socket.setTypeOfService (node:net:829:13)
at writeH1 (node:internal/deps/undici/undici:7922:16)
at Object.write (node:internal/deps/undici/undici:7666:18)
at _resume (node:internal/deps/undici/undici:9963:54)
at resume (node:internal/deps/undici/undici:9896:7)
errno: -22, code: 'EINVAL', syscall: 'setTypeOfService'
Reproduced on Node 24.18.0 and 26.4.0 (macOS, Apple Silicon, Darwin 25.5) — the same crash with both bundled undici versions, so LTS is affected too.
Impact
Any app using global fetch can die mid-flight with no way to catch the error (it is thrown from the socket write path, not surfaced through the fetch promise). In our case an MCP (Model Context Protocol) stdio server process silently exited mid-request; the client only saw "connection closed", which made this painful to diagnose.
Reproduction notes
We could not reduce it to a one-liner — plain fetch() against IPv4/IPv6/localhost/https targets succeeds on the same machine. The crash occurs reliably inside a real application (@steipete/oracle 0.15.2, during HTTP calls in its Chrome-automation cleanup phase), so the EINVAL seems to depend on socket state (e.g. connection reuse / socket mid-teardown) rather than address family. Happy to run diagnostics against a debug build if that helps pin it down.
Suggested fix
IP QoS marking is inherently best-effort; a failed setsockopt should never take the process down:
Wrap the setTypeOfService call in try/catch (ignore or debug-log failures), and/or
Skip the call entirely when typeOfService is unset/0, so the default path never touches setsockopt.
Related: this call was introduced with the IP prioritization hints feature (#4831).
Environment
undici: bundled with Node 24.18.0 and 26.4.0 (also reproduced with the copy bundled in 26.4.0)
Activity
docs: oracle 台帳に upstream issue 起票と caveat 裁定を反映
fix(server): stop a socket TOS failure from killing the whole process
undici 8, and a reset loopback connect is retried: macOS 27 resets a …
Stop counting macOS setTypeOfService EINVAL as a test failure (#389)
Upgrade Node.js to 26
Upgrade Node.js to 26
fix(ui): stop undici's setTypeOfService EINVAL from crashing main (#272)
Bound native OpenCode assigned-tool catalog projection (#17740)
test(examples): send journey fetches through undici 8 so a reset conn…
Database schema is the only schema source (spec #470) (#479)