{"record":{"id":"055efe161d5d161a","repo":"microsoft/typescript-go","slug":"syncrpcchannel-could-not-obtain-pipe-file-descrip","errorCode":null,"errorMessage":"SyncRpcChannel: could not obtain pipe file descriptors.","messagePattern":"SyncRpcChannel: could not obtain pipe file descriptors\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"_packages/native-preview/src/api/syncChannel.ts","lineNumber":195,"sourceCode":"            this.pipeFd = fd;\r\n        }\r\n        else {\r\n            // POSIX: use stdio pipe file descriptors directly.\r\n            this.child = spawn(exe, args, {\r\n                stdio: [\"pipe\", \"pipe\", \"inherit\"],\r\n            });\r\n\r\n            const stdout = this.child.stdout! as StdoutWithHandle;\r\n            const stdin = this.child.stdin! as StdinWithHandle;\r\n\r\n            this.readFd = stdout._handle.fd;\r\n            this.writeFd = stdin._handle.fd;\r\n\r\n            if (typeof this.readFd !== \"number\" || this.readFd < 0 || typeof this.writeFd !== \"number\" || this.writeFd < 0) {\r\n                stdout.destroy();\r\n                stdin.destroy();\r\n                this.child.kill();\r\n                throw new Error(\r\n                    \"SyncRpcChannel: could not obtain pipe file descriptors.\",\r\n                );\r\n            }\r\n\r\n            // Set the pipe handles to blocking mode. Under node --test's\r\n            // process isolation, pipes are created in non-blocking mode\r\n            // (for the IPC channel). This causes readSync/writeSync to get\r\n            // EAGAIN, requiring costly 1ms sleeps per retry. Setting\r\n            // blocking mode ensures readSync blocks properly until data\r\n            // arrives, matching the behavior of the native libsyncrpc.\r\n            stdout._handle.setBlocking?.(true);\r\n            stdin._handle.setBlocking?.(true);\r\n\r\n            // Prevent Node's event-loop from reading stdout or keeping the\r\n            // process alive – we will use fs.readSync exclusively.\r\n            stdout.pause();\r\n            stdout.unref();\r\n            stdin.unref();\r","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/_packages/native-preview/src/api/syncChannel.ts#L177-L213","documentation":"On POSIX, SyncRpcChannel reuses the child's stdio pipe file descriptors for synchronous I/O by reading them off Node's internal stream handles (stdout._handle.fd / stdin._handle.fd). If those internals are missing or negative — a non-Node/edge runtime, an unusual Node build, or stdio configured differently than the code expects — synchronous readSync/writeSync would be impossible, so the channel destroys the streams, kills the child, and throws.","triggerScenarios":"Running the sync client under a runtime without Node-compatible stream internals (Bun/Deno compatibility layers, patched Node, Electron with modified stdio); stdio fds already closed or remapped (fd 0/1 redirected to non-pipe files); sandboxed environments stripping handle objects.","commonSituations":"Bundling the sync client into Electron or a custom Node fork; test runners that replace process.stdout; environments where the spawn options in this code path get stdio other than ['pipe','pipe','inherit'].","solutions":["Run the sync client under a standard Node.js runtime","If embedding, ensure child stdio remains real pipes and Node stream handles are untouched","Fall back to the async API client, which uses ordinary stream I/O and does not need raw fds"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"const supportsRawFds = () => {\n  try { const c = spawn(process.execPath, [\"-e\", \"\"]]); return typeof (c.stdout?._handle as { fd?: number } | undefined)?.fd === \"number\"; } catch { return false; }\n};","typeGuard":null,"tryCatchPattern":"try { api = new API({ cwd }); } catch (e) { if ((e as Error).message.includes(\"could not obtain pipe file descriptors\")) { /* switch to async API client which uses stream I/O */ } else throw e; }","preventionTips":["Run the sync client only under standard Node.js","Do not replace or wrap child stdio streams in the host process","Fall back to the async client on runtimes without Node stream handle internals"],"tags":["typescript","posix","spawn","runtime","internal"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}