Skip to content

Bun.connect: native write() returns -1 (not raw -errno) on a peer RST and close carries ECONNRESET - #36422

Open
robobun wants to merge 12 commits into
mainfrom
farm/7badbb3f/native-socket-write-rst-errno
Open

Bun.connect: native write() returns -1 (not raw -errno) on a peer RST and close carries ECONNRESET#36422
robobun wants to merge 12 commits into
mainfrom
farm/7badbb3f/native-socket-write-rst-errno

Bun.connect: native write() on a peer-RST'd socket returns -1 and clo…

546df30
Select commit
Loading
Failed to load commit list.
Claude / Claude Code Review completed Jul 30, 2026 in 33m 10s

Code review found 3 important issues

Found 4 candidates, confirmed 3. See review comments for details.

Details

Severity Count
🔴 Important 3
🟡 Nit 0
🟣 Pre-existing 0
Severity File:Line Issue
🔴 Important src/runtime/socket/socket_body.rs:1593-1598 on_end suppression never closes an allowHalfOpen socket — close(ECONNRESET) never fires
🔴 Important test/js/bun/net/socket.test.ts:3420-3423 RST test asserts Linux-only errno shape; will fail on macOS CI
🔴 Important src/runtime/socket/socket_body.rs:279-283 fatal_write_errno not reset on socket reconnect path

Annotations

Check failure on line 1598 in src/runtime/socket/socket_body.rs

See this annotation in the file changed.

@claude claude / Claude Code Review

on_end suppression never closes an allowHalfOpen socket — close(ECONNRESET) never fires

For `Bun.connect({ allowHalfOpen: true })`, this early return relies on "let `on_close` report the error", but with `allow_half_open` set loop.c never auto-closes after `on_end` — it only re-arms WRITABLE — so `on_close` never fires and the promised `close(ECONNRESET)` never arrives. The socket instead spins on level-triggered EPOLLHUP (each poll: WRITABLE→`on_writable` no-op, then eof→`on_end` suppressed→re-arm WRITABLE), and pre-PR the user at least got an `end` hook to call `socket.end()` and

Check failure on line 3423 in test/js/bun/net/socket.test.ts

See this annotation in the file changed.

@claude claude / Claude Code Review

RST test asserts Linux-only errno shape; will fail on macOS CI

The last two assertions (`code === "ECONNRESET"` and `syscall === "write"`) encode Linux errno behavior; on macOS, `send()` to a peer-RST'd socket returns `EPIPE` without consuming `so_error`, so the close error surfaces as either `{code: "EPIPE", syscall: "write"}` or `{code: "ECONNRESET", syscall: "read"}` — the conjunction cannot hold and the test will fail on macOS CI. Either gate these two lines on `isLinux`, or relax to `expect(["ECONNRESET","EPIPE"]).toContain(code)` / `expect(["read","wr

Check failure on line 283 in src/runtime/socket/socket_body.rs

See this annotation in the file changed.

@claude claude / Claude Code Review

fatal_write_errno not reset on socket reconnect path

`fatal_write_errno` is never cleared when a `NewSocket` wrapper is reused for reconnect (`detach_for_reconnect`, `connect_finish`'s `maybe_previous` branch, and the Windows named-pipe `prev` branches all skip it). A node:net socket that saw a peer RST, then reconnects — the code comments cite the MongoDB driver as a real user of this path — will on the *new* connection suppress `end` on a clean FIN (line 1593) and fabricate a spurious `ECONNRESET` / `syscall: 'write'` in `on_close` (line 2068) f