From e3724fef7523a3af63c50f962aa45af7e3c18f3e Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Fri, 30 Oct 2026 10:53:19 -0700 Subject: [PATCH] Fail a missing checker server at startup instead of at its first read The POSIX launcher backgrounded a subshell that blocked opening its input FIFO, so a missing executable looked started and its transport stayed tracked with an exit hook. Check the executable before creating the FIFOs. Co-Authored-By: Claude 5.5 --- .../compiler/src/frontend/ts7/rpc-process.test.ts | 13 ++++++------- packages/compiler/src/frontend/ts7/rpc-process.ts | 7 +++++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/compiler/src/frontend/ts7/rpc-process.test.ts b/packages/compiler/src/frontend/ts7/rpc-process.test.ts index e91b0b8200..91d07590ee 100644 --- a/packages/compiler/src/frontend/ts7/rpc-process.test.ts +++ b/packages/compiler/src/frontend/ts7/rpc-process.test.ts @@ -30,13 +30,12 @@ test.skipIf(!posix)("a FIFO server echoes, and exits when its transport closes", expect(alive(io.pid)).toBe(false); }); -test.skipIf(!posix)("a FIFO server that cannot start fails its first read", () => { - const io = new Ts7FifoIo("/nonexistent/scriptc-ts7-server", []); - try { - expect(io.read(Buffer.alloc(1), 0, 1)).toBe(0); - } finally { - io.close(); - } +test.skipIf(!posix)("a missing FIFO server fails at construction and tracks nothing", () => { + const before = process.listenerCount("exit"); + expect(() => new Ts7FifoIo("/nonexistent/scriptc-ts7-server", [])).toThrow( + "Unable to start TypeScript server", + ); + expect(process.listenerCount("exit")).toBe(before); }); test.skipIf(!posix)( diff --git a/packages/compiler/src/frontend/ts7/rpc-process.ts b/packages/compiler/src/frontend/ts7/rpc-process.ts index 24d8f5a99a..bdcf30478f 100644 --- a/packages/compiler/src/frontend/ts7/rpc-process.ts +++ b/packages/compiler/src/frontend/ts7/rpc-process.ts @@ -135,9 +135,12 @@ class Ts7NamedPipeIo implements Ts7WireIo { * this process would stay a zombie after it exits: Node reaps children only * from its event loop, which a synchronous compile never returns to. The * subshell points its standard streams at /dev/null before it opens the - * FIFOs, so spawnSync's output pipe closes as soon as the shell exits. */ + * FIFOs, so spawnSync's output pipe closes as soon as the shell exits. A + * missing or non-executable server fails here, before any FIFO exists: the + * background subshell would otherwise block opening its input and the failure + * would only surface as end of file on the first read. */ const FIFO_LAUNCH = - 'i=$1 o=$2; shift 2; mkfifo -m 600 "$i" "$o" || exit 1; ' + + 'i=$1 o=$2; shift 2; command -v "$1" >/dev/null || exit 127; mkfifo -m 600 "$i" "$o" || exit 1; ' + '( exec /dev/null; exec "$@" <"$i" >"$o" ) & echo $!'; /** How long a starting server may take to open its input, in milliseconds. */