{"record":{"id":"b9011391186a3999","repo":"openai/codex-plugin-cc","slug":"codex-app-server-client-is-closed","errorCode":null,"errorMessage":"codex app-server client is closed.","messagePattern":"codex app-server client is closed\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/codex/scripts/lib/app-server.mjs","lineNumber":88,"sourceCode":"\n    this.exitPromise = new Promise((resolve) => {\n      this.resolveExit = resolve;\n    });\n  }\n\n  setNotificationHandler(handler) {\n    this.notificationHandler = handler;\n  }\n\n  /**\n   * @template {AppServerMethod} M\n   * @param {M} method\n   * @param {import(\"./app-server-protocol\").AppServerRequestParams<M>} params\n   * @returns {Promise<import(\"./app-server-protocol\").AppServerResponse<M>>}\n   */\n  request(method, params) {\n    if (this.closed) {\n      throw new Error(\"codex app-server client is closed.\");\n    }\n\n    const id = this.nextId;\n    this.nextId += 1;\n\n    return new Promise((resolve, reject) => {\n      this.pending.set(id, { resolve, reject, method });\n      this.sendMessage({ id, method, params });\n    });\n  }\n\n  notify(method, params = {}) {\n    if (this.closed) {\n      return;\n    }\n    this.sendMessage({ method, params });\n  }\n","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/openai/codex-plugin-cc/blob/db52e28f4d9ded852ab3942cea316258ae4ef346/plugins/codex/scripts/lib/app-server.mjs#L70-L106","documentation":"AppServerClientBase.request() is the JSON-RPC send method. It checks this.closed first; if close() was already called (or handleExit set closed state), any further request() throws synchronously. This prevents sending on a torn-down transport. Note notify() silently returns when closed, but request() throws because a response is expected.","triggerScenarios":"Calling client.request(...) after await client.close(), or after the underlying process/socket exited (handleExit does not set this.closed, but a subsequent manual close does). Also calling request during shutdown sequencing.","commonSituations":"A shutdown race where the application closes the client but a pending turn/review call still issues request(); retry logic that does not check closed state.","solutions":["Guard every request() call with a closed check, or track lifecycle so no requests are issued after close().","Create a fresh client via CodexAppServerClient.connect() for a new session rather than reusing a closed one.","Ensure close() is the last operation and awaited before the client goes out of scope."],"exampleFix":"// before\nawait client.close();\nawait client.request('turn/start', params); // throws\n// after\nconst result = await client.request('turn/start', params);\nawait client.close();","handlingStrategy":"try-catch","validationCode":"if (client.closed) throw new Error('client closed; reconnect');","typeGuard":"/** @param {{closed: boolean}} c @returns {boolean} */\nfunction isClientOpen(c) { return !c.closed; }","tryCatchPattern":"try {\n  await client.request(method, params);\n} catch (e) {\n  if (/is closed/.test(e.message)) { /* recreate client */ }\n  else throw e;\n}","preventionTips":["Never call request() after close(); recreate the client instead.","Centralize request issuance behind a lifecycle-aware wrapper."],"tags":["app-server","lifecycle","state","json-rpc"],"backgroundTag":null,"analyzedSha":"db52e28f4d9ded852ab3942cea316258ae4ef346","analyzedAt":"2026-08-13T05:13:20.855Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}