{"record":{"id":"d5a909a80e992175","repo":"microsoft/typescript-go","slug":"syncrpcchannel-is-closed","errorCode":null,"errorMessage":"SyncRpcChannel is closed","messagePattern":"SyncRpcChannel is closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"_packages/native-preview/src/api/syncChannel.ts","lineNumber":272,"sourceCode":"            }\r\n            // Destroy the stdio streams so that their pipe handles are closed\r\n            // and no longer prevent the event loop from draining.\r\n            this.child.stdout?.destroy();\r\n            this.child.stdin?.destroy();\r\n            this.child.kill();\r\n            this.readFd = -1;\r\n            this.writeFd = -1;\r\n        }\r\n        catch {\r\n            // swallow – process may already be dead\r\n        }\r\n    }\r\n\r\n    // ── Core request loop ───────────────────────────────────────────\r\n\r\n    private ensureOpen(): void {\r\n        if (this.readFd < 0) {\r\n            throw new Error(\"SyncRpcChannel is closed\");\r\n        }\r\n    }\r\n\r\n    private getMethodBuf(method: string): Buffer {\r\n        let buf = this.methodBufCache.get(method);\r\n        if (buf === undefined) {\r\n            buf = Buffer.from(method, \"utf-8\");\r\n            this.methodBufCache.set(method, buf);\r\n        }\r\n        return buf;\r\n    }\r\n\r\n    private requestBytesSync(method: string, payload: Buffer | Uint8Array | string): Buffer {\r\n        const methodBuf = this.getMethodBuf(method);\r\n        if (this.collectTiming) {\r\n            this.lastBytesSent = typeof payload === \"string\"\r\n                ? Buffer.byteLength(payload, \"utf-8\")\r\n                : payload.length;\r","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/_packages/native-preview/src/api/syncChannel.ts#L254-L290","documentation":"Every request goes through ensureOpen(), which throws 'SyncRpcChannel is closed' once readFd is negative — the sentinel set by close() (and the cleanup path when the child dies). The channel is single-use: after closing (explicitly or because the child terminated), any further apiRequest/request call fails fast instead of writing to dead file descriptors.","triggerScenarios":"Calling any API method after client.close(); issuing requests after the tsgo child crashed and the channel cleaned up; a request racing close() from another synchronous entry point.","commonSituations":"Editor shutdown ordering bugs (dispose runs while queued queries still execute); forgetting that API.close() cascades to the channel; long-lived singletons that get closed on error then reused.","solutions":["Track closed state in your own wrapper and skip or re-create requests after close()","Serialize close() with pending requests (same tick ordering) since the channel is synchronous","Recreate the Client/API instance if you need to keep working after a deliberate close"],"exampleFix":"// before\napi.close();\napi.getProjectSnapshot(\"tsconfig.json\"); // channel closed -> throws\n\n// after\napi.close();\napi = new API({ cwd }); // fresh instance\napi.getProjectSnapshot(\"tsconfig.json\");","handlingStrategy":"validation","validationCode":"class SafeApi { constructor(private api: API) {}\n private closed = false;\n close() { this.closed = true; this.api.close(); }\n getProjectSnapshot(p: string) {\n   if (this.closed) throw new Error(\"API already closed; recreate it\");\n   return this.api.getProjectSnapshot(p);\n } }","typeGuard":null,"tryCatchPattern":"try { result = api.someMethod(); } catch (e) { if ((e as Error).message === \"SyncRpcChannel is closed\") { api = new API({ cwd }); result = api.someMethod(); } else throw e; }","preventionTips":["Wrap the API and record closed state; block requests after close()","Serialize close with pending synchronous requests","Create a new Client/API instance when work must continue after close"],"tags":["typescript","rpc","lifecycle","channel-closed"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}