From ba31e740030aad2963cb2304aae468437f4b9e58 Mon Sep 17 00:00:00 2001 From: rishi-chauhan Date: Sat, 28 Mar 2026 17:50:16 +0530 Subject: [PATCH 1/3] fix(server): fall back to direct fd read when /proc/self/fd path fails On some Linux environments (containers, restricted kernels), `/proc/self/fd/` exists but cannot be opened, returning ENXIO, EINVAL, or EPERM. Previously this would surface as a hard BootstrapError. Now the server falls back to reading the fd directly, matching the behavior already used on Windows. Also prefers `fs.createReadStream` over `net.Socket` for the direct fd path, as it is better behaved for regular file descriptors. --- apps/server/src/bootstrap.test.ts | 40 +++++++++++++++++++++++++ apps/server/src/bootstrap.ts | 50 +++++++++++++++++++++++-------- 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/apps/server/src/bootstrap.test.ts b/apps/server/src/bootstrap.test.ts index 804f2440a9..13a386a64e 100644 --- a/apps/server/src/bootstrap.test.ts +++ b/apps/server/src/bootstrap.test.ts @@ -8,6 +8,7 @@ import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import { TestClock } from "effect/testing"; +import { vi } from "vitest"; import { readBootstrapEnvelope, resolveFdPath } from "./bootstrap"; import { assertNone, assertSome } from "@effect/vitest/utils"; @@ -47,6 +48,45 @@ it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => { }), ); + it.effect("falls back to reading the inherited fd when path duplication fails", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const filePath = yield* fs.makeTempFileScoped({ prefix: "t3-bootstrap-", suffix: ".ndjson" }); + + yield* fs.writeFileString( + filePath, + `${yield* Schema.encodeEffect(Schema.fromJsonString(TestEnvelopeSchema))({ + mode: "desktop", + })}\n`, + ); + + const fd = yield* Effect.acquireRelease( + Effect.sync(() => NFS.openSync(filePath, "r")), + (fd) => Effect.sync(() => NFS.closeSync(fd)), + ); + + const originalOpenSync = NFS.openSync; + const openSync = vi.spyOn(NFS, "openSync").mockImplementation((path, flags) => { + if (path === "/proc/self/fd/3" && flags === "r") { + const error = new Error("no such device or address"); + Object.assign(error, { code: "ENXIO" }); + throw error; + } + + return originalOpenSync(path, flags); + }); + + try { + const payload = yield* readBootstrapEnvelope(TestEnvelopeSchema, fd, { timeoutMs: 100 }); + assertSome(payload, { + mode: "desktop", + }); + } finally { + openSync.mockRestore(); + } + }), + ); + it.effect("returns none when the fd is unavailable", () => Effect.gen(function* () { const fd = NFS.openSync("/dev/null", "r"); diff --git a/apps/server/src/bootstrap.ts b/apps/server/src/bootstrap.ts index b837ac6c18..c6132ad2cc 100644 --- a/apps/server/src/bootstrap.ts +++ b/apps/server/src/bootstrap.ts @@ -108,21 +108,22 @@ const makeBootstrapInputStream = (fd: number) => try: () => { const fdPath = resolveFdPath(fd); if (fdPath === undefined) { - const stream = new Net.Socket({ - fd, - readable: true, - writable: false, - }); - stream.setEncoding("utf8"); - return stream; + return makeDirectBootstrapStream(fd); } - const streamFd = NFS.openSync(fdPath, "r"); - return NFS.createReadStream("", { - fd: streamFd, - encoding: "utf8", - autoClose: true, - }); + try { + const streamFd = NFS.openSync(fdPath, "r"); + return NFS.createReadStream("", { + fd: streamFd, + encoding: "utf8", + autoClose: true, + }); + } catch (error) { + if (isBootstrapFdPathDuplicationError(error)) { + return makeDirectBootstrapStream(fd); + } + throw error; + } }, catch: (error) => new BootstrapError({ @@ -131,6 +132,29 @@ const makeBootstrapInputStream = (fd: number) => }), }); +const makeDirectBootstrapStream = (fd: number): Readable => { + try { + return NFS.createReadStream("", { + fd, + encoding: "utf8", + autoClose: true, + }); + } catch { + const stream = new Net.Socket({ + fd, + readable: true, + writable: false, + }); + stream.setEncoding("utf8"); + return stream; + } +}; + +const isBootstrapFdPathDuplicationError = Predicate.compose( + Predicate.hasProperty("code"), + (_) => _.code === "ENXIO" || _.code === "EINVAL" || _.code === "EPERM", +); + export function resolveFdPath( fd: number, platform: NodeJS.Platform = process.platform, From 04a1c7951a818aa813c219a632a6807f83e4f778 Mon Sep 17 00:00:00 2001 From: rishi-chauhan Date: Sat, 28 Mar 2026 18:23:50 +0530 Subject: [PATCH 2/3] fix(server): fix bootstrap fallback test mock and fd leak on duplication error --- apps/server/src/bootstrap.test.ts | 41 +++++++++++++++++++------------ apps/server/src/bootstrap.ts | 6 ++++- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/apps/server/src/bootstrap.test.ts b/apps/server/src/bootstrap.test.ts index 13a386a64e..5eececef2c 100644 --- a/apps/server/src/bootstrap.test.ts +++ b/apps/server/src/bootstrap.test.ts @@ -13,6 +13,24 @@ import { vi } from "vitest"; import { readBootstrapEnvelope, resolveFdPath } from "./bootstrap"; import { assertNone, assertSome } from "@effect/vitest/utils"; +const openSyncInterceptor = vi.hoisted(() => ({ failPath: null as string | null })); + +vi.mock("node:fs", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + openSync: (...args: Parameters) => { + const [filePath, flags] = args; + if (typeof filePath === "string" && filePath === openSyncInterceptor.failPath && flags === "r") { + const error = new Error("no such device or address"); + Object.assign(error, { code: "ENXIO" }); + throw error; + } + return (actual.openSync as (...a: typeof args) => number)(...args); + }, + }; +}); + const TestEnvelopeSchema = Schema.Struct({ mode: Schema.String }); it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => { @@ -60,29 +78,20 @@ it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => { })}\n`, ); - const fd = yield* Effect.acquireRelease( - Effect.sync(() => NFS.openSync(filePath, "r")), - (fd) => Effect.sync(() => NFS.closeSync(fd)), - ); - - const originalOpenSync = NFS.openSync; - const openSync = vi.spyOn(NFS, "openSync").mockImplementation((path, flags) => { - if (path === "/proc/self/fd/3" && flags === "r") { - const error = new Error("no such device or address"); - Object.assign(error, { code: "ENXIO" }); - throw error; - } - - return originalOpenSync(path, flags); - }); + // Open without acquireRelease: the direct-stream fallback uses autoClose: true, + // so the stream owns the fd lifecycle and closes it asynchronously on end. + // Attempting to also close it synchronously in a finalizer races with the + // stream's async close and produces an uncaught EBADF. + const fd = NFS.openSync(filePath, "r"); + openSyncInterceptor.failPath = `/proc/self/fd/${fd}`; try { const payload = yield* readBootstrapEnvelope(TestEnvelopeSchema, fd, { timeoutMs: 100 }); assertSome(payload, { mode: "desktop", }); } finally { - openSync.mockRestore(); + openSyncInterceptor.failPath = null; } }), ); diff --git a/apps/server/src/bootstrap.ts b/apps/server/src/bootstrap.ts index c6132ad2cc..0fb1352268 100644 --- a/apps/server/src/bootstrap.ts +++ b/apps/server/src/bootstrap.ts @@ -111,8 +111,9 @@ const makeBootstrapInputStream = (fd: number) => return makeDirectBootstrapStream(fd); } + let streamFd: number | undefined; try { - const streamFd = NFS.openSync(fdPath, "r"); + streamFd = NFS.openSync(fdPath, "r"); return NFS.createReadStream("", { fd: streamFd, encoding: "utf8", @@ -120,6 +121,9 @@ const makeBootstrapInputStream = (fd: number) => }); } catch (error) { if (isBootstrapFdPathDuplicationError(error)) { + if (streamFd !== undefined) { + NFS.closeSync(streamFd); + } return makeDirectBootstrapStream(fd); } throw error; From 286e7c2b1f1caff2fc34b419ae849ce7230e5a81 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 28 Mar 2026 11:39:27 -0700 Subject: [PATCH 3/3] Format bootstrap test ENXIO fixture match - Wrap the `openSync` ENXIO guard across multiple lines - Keep the Linux fd failure test stable and readable --- apps/server/src/bootstrap.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/server/src/bootstrap.test.ts b/apps/server/src/bootstrap.test.ts index 5eececef2c..3fce6af9c4 100644 --- a/apps/server/src/bootstrap.test.ts +++ b/apps/server/src/bootstrap.test.ts @@ -21,7 +21,11 @@ vi.mock("node:fs", async (importOriginal) => { ...actual, openSync: (...args: Parameters) => { const [filePath, flags] = args; - if (typeof filePath === "string" && filePath === openSyncInterceptor.failPath && flags === "r") { + if ( + typeof filePath === "string" && + filePath === openSyncInterceptor.failPath && + flags === "r" + ) { const error = new Error("no such device or address"); Object.assign(error, { code: "ENXIO" }); throw error;