{"record":{"id":"b0bfd14f16a07a0e","repo":"can1357/oh-my-pi","slug":"daemon-broker-client-is-closed","errorCode":null,"errorMessage":"Daemon broker client is closed","messagePattern":"Daemon broker client is closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/launch/client.ts","lineNumber":164,"sourceCode":"\treadonly #completionReplays = new Set<string>();\n\treadonly #inFlightCompletionIds = new Set<string>();\n\treadonly #completionSubscriptionId = crypto.randomUUID();\n\t#socket: net.Socket | undefined;\n\t#connectPromise: Promise<void> | undefined;\n\t#buffer = \"\";\n\t#closed = false;\n\t#completionReconnectTimer: NodeJS.Timeout | undefined;\n\n\tconstructor(projectDir: string, runtimeDir: string, token: string, options: DaemonBrokerClientOptions) {\n\t\tthis.projectDir = projectDir;\n\t\tthis.#runtimeDir = runtimeDir;\n\t\tthis.#endpoint = daemonBrokerEndpoint(projectDir, runtimeDir);\n\t\tthis.#token = token;\n\t\tthis.#idleGraceMs = options.idleGraceMs;\n\t}\n\n\tasync request(operation: DaemonOperation, signal?: AbortSignal): Promise<DaemonRpcResult> {\n\t\tif (this.#closed) throw new Error(\"Daemon broker client is closed\");\n\t\tif (signal?.aborted) throw new Error(\"Daemon broker request aborted\");\n\t\tawait this.#connect();\n\t\tconst socket = this.#socket;\n\t\tif (!socket || socket.destroyed) throw new Error(\"Daemon broker socket is unavailable\");\n\n\t\tconst completionUnsubscribes = [...this.#completionUnsubscribes];\n\t\tconst completionReplays = [...this.#completionReplays];\n\t\tconst id = crypto.randomUUID();\n\t\tconst { promise, resolve, reject } = Promise.withResolvers<DaemonRpcResult>();\n\t\tconst timer = setTimeout(() => {\n\t\t\tconst pending = this.#pending.get(id);\n\t\t\tif (!pending) return;\n\t\t\tthis.#pending.delete(id);\n\t\t\tpending.removeAbort?.();\n\t\t\treject(new Error(`Daemon ${operation.op} request timed out`));\n\t\t}, requestTimeoutMs(operation));\n\t\tconst pending: PendingRequest = { operation, resolve, reject, timer };\n\t\tif (signal) {","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/launch/client.ts#L146-L182","documentation":"DaemonBrokerClient.request rejects any RPC issued after the client was closed via close(). #closed is a one-way latch; once closed the socket and subscription state are torn down and no further operations are permitted.","triggerScenarios":"Calling request() after awaiting close(); fire-and-forget async work (like #publishCompletionOwners) issuing a request after the session shut down; reusing a cached client instance across a restart cycle.","commonSituations":"App shutdown ordering where background completions still publish; hot-reload or restart logic that closes the client but keeps stale references; long-lived callbacks outliving the client.","solutions":["Check client.isClosed (or track close state) before issuing requests","Cancel/await pending background work (#publishCompletionOwners) before calling close()","Create a new DaemonBrokerClient instance after close instead of reusing the old one"],"exampleFix":"// before\nawait client.close();\nawait client.request(op); // throws\n// after\nawait client.close();\nconst client2 = new DaemonBrokerClient(projectDir, runtimeDir, token);\nawait client2.request(op);","handlingStrategy":"try-catch","validationCode":"if (client.isClosed) throw new Error('cannot request: broker client already closed');","typeGuard":null,"tryCatchPattern":"try {\n  await client.request(op);\n} catch (err) {\n  if (err.message === 'Daemon broker client is closed') {\n    client = new DaemonBrokerClient(projectDir, runtimeDir, token); // recreate instead of failing\n    await client.request(op);\n  } else throw err;\n}","preventionTips":["Track client lifecycle: close() only after all background publishers have settled","Use AbortController/cancellation to stop background work before close()","Avoid caching client instances across restart cycles"],"tags":["ipc","lifecycle","daemon","state"],"backgroundTag":"client-closed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}