{"record":{"id":"374bb75e9b79d95f","repo":"openai/codex","slug":"wouldblock-374bb7","errorCode":"WouldBlock","errorMessage":"process is starting","messagePattern":"process is starting","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"warning","filePath":"codex-rs/rmcp-client/src/executor_process_transport.rs","lineNumber":258,"sourceCode":"        async move {\n            let _stdin_write_permit = stdin_write_semaphore\n                .acquire()\n                .await\n                .map_err(io::Error::other)?;\n            // rmcp hands us a structured JSON-RPC message. Stdio transport on\n            // the wire is JSON plus one newline delimiter.\n            let mut bytes = to_vec(&item).map_err(io::Error::other)?;\n            bytes.push(b'\\n');\n            let response = process.write(bytes).await.map_err(io::Error::other)?;\n            match response.status {\n                WriteStatus::Accepted => Ok(()),\n                WriteStatus::UnknownProcess => {\n                    Err(io::Error::new(io::ErrorKind::BrokenPipe, \"unknown process\"))\n                }\n                WriteStatus::StdinClosed => {\n                    Err(io::Error::new(io::ErrorKind::BrokenPipe, \"stdin closed\"))\n                }\n                WriteStatus::Starting => Err(io::Error::new(\n                    io::ErrorKind::WouldBlock,\n                    \"process is starting\",\n                )),\n            }\n        }\n    }\n\n    fn receive(&mut self) -> impl Future<Output = Option<RxJsonRpcMessage<RoleClient>>> + Send {\n        self.receive_message()\n    }\n\n    async fn close(&mut self) -> std::result::Result<(), Self::Error> {\n        self.process.terminate().await.map_err(io::Error::other)?;\n        self.terminated = true;\n        Ok(())\n    }\n}\n","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/rmcp-client/src/executor_process_transport.rs#L240-L276","documentation":"Thrown by the stdio MCP transport's send() when the executor accepts the write request but reports WriteStatus::Starting — the remote MCP server process was started, yet its stdin is not ready to accept data yet. The io::ErrorKind::WouldBlock kind is deliberate: it marks a transient, retryable condition, in contrast to the BrokenPipe variants used for a dead or closed stdin. Writes are serialized under a per-transport semaphore, so concurrent senders queue up and the earliest ones can hit this during process warm-up.","triggerScenarios":"Calling Transport::send (any JSON-RPC request, e.g. the initial 'initialize') in the window after process/start succeeds but before the executor marks stdin ready; a burst of concurrent stdin writes immediately after connecting to a remote executor, as exercised by the serializes_concurrent_stdin_writes test.","commonSituations":"Cold-starting containers or remote runtimes where the MCP server binary takes time to open stdin; reconnecting while the executor is restarting the process; racing the first initialize against process readiness; slow disks or image pulls delaying exec.","solutions":["Retry the send on WouldBlock with a short backoff (tens of milliseconds) — the process usually becomes ready quickly","If you control the flow, wait for process readiness events (Exited/Closed never arriving, first output seen) before issuing requests","Do not treat this like BrokenPipe: 'unknown process'/'stdin closed' are terminal, 'process is starting' is not","If it persists for seconds, investigate why the remote process never reaches ready state (check executor logs)"],"exampleFix":"// before\ntransport.send(message).await?; // Err(WouldBlock, 'process is starting')\n\n// after\nloop {\n    match transport.send(message).await {\n        Ok(()) => break,\n        Err(e) if e.kind() == io::ErrorKind::WouldBlock => {\n            tokio::time::sleep(std::time::Duration::from_millis(25)).await;\n        }\n        Err(e) => return Err(e.into()), // BrokenPipe etc. is terminal\n    }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"fn is_process_starting(error: &std::io::Error) -> bool {\n    error.kind() == std::io::ErrorKind::WouldBlock\n}","tryCatchPattern":"// WouldBlock is transient; BrokenPipe is terminal — distinguish them\nloop {\n    match transport.send(message).await {\n        Ok(()) => break,\n        Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {\n            tokio::time::sleep(std::time::Duration::from_millis(25)).await;\n        }\n        Err(e) => return Err(e.into()),\n    }\n}","preventionTips":["Serialize sends behind your own queue so only the first request hits the startup window","Allow a bounded startup grace period (e.g. 2-5s of WouldBlock retries) before surfacing an error","Never map WouldBlock from this transport to a fatal state — only BrokenPipe variants are fatal"],"tags":["rust","mcp","stdio-transport","wouldblock","retry","process-startup"],"backgroundTag":"would-block-retry","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}