Skip to content

fix(sidecar): attach error listener on server-side IPC sockets#230

Open
Foximo24 wants to merge 1 commit intonexu-io:mainfrom
Foximo24:fix/sidecar-ipc-server-error-listener
Open

fix(sidecar): attach error listener on server-side IPC sockets#230
Foximo24 wants to merge 1 commit intonexu-io:mainfrom
Foximo24:fix/sidecar-ipc-server-error-listener

Conversation

@Foximo24
Copy link
Copy Markdown
Contributor

@Foximo24 Foximo24 commented May 1, 2026

Summary

The IPC server in createJsonIpcServer (packages/sidecar/src/index.ts) attached only a data handler to incoming sockets. The server's response is sent via socket.end(payload), which races with the client's own socket.end() in requestJsonIpc (called immediately after reading the response). On Windows named pipes with Node 24 this race produces an EPIPE write error on the server side. With no error listener, that error is unhandled and crashes the entire daemon process, so tools-dev reports daemon did not expose status in time.

This adds a no-op error listener on the connection socket to absorb benign close-races. The client side (requestJsonIpc) already has an error listener, so this just matches the existing pattern.

Repro

  • Windows 11 Pro + Node v24.14.0 + pnpm 10.33.2
  • pnpm tools-dev start daemon consistently crashes with EPIPE from Socket.end at packages/sidecar/dist/index.mjs:319 (the socket.end(\${...}\n`)` line)
  • After the patch: daemon stays up, status reports running cleanly

Test plan

  • Manual repro on Win11 / Node 24 β€” daemon now starts cleanly and serves IPC
  • Verify existing packages/sidecar/src/index.test.ts still passes
  • No behavior change on POSIX (the listener is a no-op)

The IPC server in createJsonIpcServer attached only a 'data' handler to
incoming sockets. The server's response is sent via socket.end(payload),
which races with the client's own socket.end() in requestJsonIpc (called
immediately after reading the response). On Windows named pipes with
Node 24 this race produces an EPIPE write error on the server side. With
no 'error' listener, that error is unhandled and crashes the entire
daemon process, so tools-dev reports "daemon did not expose status in time".

Adding a no-op error listener on the connection socket absorbs benign
close-races without affecting normal operation. The client side
already has an error listener, so this matches the existing pattern.

Reproduced deterministically on Windows 11 + Node v24.14.0.
@lefarcen lefarcen added the bug Something isn't working label May 1, 2026
@lefarcen
Copy link
Copy Markdown
Contributor

lefarcen commented May 1, 2026

Hi @Foximo24! πŸŽ‰
Thanks for the contribution β€” great catch on the Windows IPC race condition causing daemon crashes.
I will run a deep review and get back to you within 24h.

Thanks for making open-design better!
β€” open-design team

Copy link
Copy Markdown
Contributor

@lefarcen lefarcen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Foximo24! πŸ‘‹ Thanks for the detailed repro β€” the Windows IPC race crash is a real production issue.

Code review (Lens A):

βœ… Root cause analysis is sound: The server calls socket.end(payload) and the client calls socket.end() immediately after reading the response β€” that's a classic write-after-end race on Windows named pipes with Node 24.

βœ… Pattern consistency: You're right that the client side (requestJsonIpc line 562) already has an error listener, so this mirrors that.

Suggestions (non-blocking):

The no-op socket.on("error", () => {}) swallows ALL socket errors, not just benign close races. If a real error occurs (e.g., unauthorized connection attempt, network issue), it's now invisible. Consider one of these:

  1. Filter by error code (most robust):

    socket.on("error", (err: NodeJS.ErrnoException) => {
      if (err.code === "EPIPE" || err.code === "ECONNRESET") return; // benign close-race
      console.warn("[sidecar] IPC socket error:", err); // log real issues
    });
  2. Or at minimum, log silently so you can debug future issues:

    socket.on("error", (err) => {
      if (process.env.DEBUG) console.warn("[sidecar] IPC socket error:", err);
    });

Testing gap: You mentioned "verify existing test passes" but packages/sidecar/src/index.test.ts has no tests for createJsonIpcServer/requestJsonIpc β€” they only test path resolution. That's a broader gap (not your fault), but means this fix can't be validated automatically.

The core fix is correct and solves the daemon crash. The suggestions above would make it more maintainable long-term, but they're not blockers.

Thanks for tackling this Windows-specific issue β€” it's easy to miss cross-platform IPC subtleties! πŸš€

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants