{"record":{"id":"46634f7c1d6263fc","repo":"openai/codex-plugin-cc","slug":"codex-app-server-stdin-is-not-available","errorCode":null,"errorMessage":"codex app-server stdin is not available.","messagePattern":"codex app-server stdin is not available\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/codex/scripts/lib/app-server.mjs","lineNumber":272,"sourceCode":"            } catch {\n              // Best-effort cleanup inside an unref'd timer — swallow errors\n              // to avoid crashing the host process during shutdown.\n            }\n          } else {\n            this.proc.kill(\"SIGTERM\");\n          }\n        }\n      }, 50).unref?.();\n    }\n\n    await this.exitPromise;\n  }\n\n  sendMessage(message) {\n    const line = `${JSON.stringify(message)}\\n`;\n    const stdin = this.proc?.stdin;\n    if (!stdin) {\n      throw new Error(\"codex app-server stdin is not available.\");\n    }\n    stdin.write(line);\n  }\n}\n\nclass BrokerCodexAppServerClient extends AppServerClientBase {\n  constructor(cwd, options = {}) {\n    super(cwd, options);\n    this.transport = \"broker\";\n    this.endpoint = options.brokerEndpoint;\n  }\n\n  async initialize() {\n    await new Promise((resolve, reject) => {\n      const target = parseBrokerEndpoint(this.endpoint);\n      this.socket = net.createConnection({ path: target.path });\n      this.socket.setEncoding(\"utf8\");\n      this.socket.on(\"connect\", resolve);","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/openai/codex-plugin-cc/blob/db52e28f4d9ded852ab3942cea316258ae4ef346/plugins/codex/scripts/lib/app-server.mjs#L254-L290","documentation":"SpawnedCodexAppServerClient.sendMessage writes a JSONL line to this.proc.stdin. If proc is null or proc.stdin is null/destroyed (process not spawned, already exited, or stdin closed during shutdown), it throws. This is the direct-transport variant; the broker variant has its own socket check.","triggerScenarios":"Calling request()/notify() before initialize() has spawned the process (proc undefined), or after the codex process exited and stdin is gone. close() ends stdin, so a post-close request hits this.","commonSituations":"A race where handleExit fires (process crash) but a queued request still calls sendMessage; or invoking request before awaiting initialize().","solutions":["Always await client.initialize() (or use CodexAppServerClient.connect which calls it) before any request.","Do not issue requests after close() or after observing process exit; check the closed/exit state.","Handle process exit errors (handleExit rejects pending requests) and recreate the client if needed."],"exampleFix":"// before\nclient.request('turn/start', params); // before initialize / after exit\n// after\nawait client.initialize();\nawait client.request('turn/start', params);","handlingStrategy":"validation","validationCode":"if (!client.proc?.stdin) throw new Error('stdin unavailable; (re)initialize');","typeGuard":"/** @param {object} c @returns {boolean} */\nfunction hasStdin(c) { return Boolean(c.proc?.stdin); }","tryCatchPattern":"try {\n  await client.request(method, params);\n} catch (e) {\n  if (/stdin is not available/.test(e.message)) { /* reinitialize */ }\n  else throw e;\n}","preventionTips":["Await initialize()/connect() before sending.","Treat process-exit events as terminal and recreate the client."],"tags":["app-server","lifecycle","transport","spawn"],"backgroundTag":null,"analyzedSha":"db52e28f4d9ded852ab3942cea316258ae4ef346","analyzedAt":"2026-08-13T05:13:20.855Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}