openai/codex · warning · std::io::Error
WouldBlock
WouldBlock
Error message
process is starting
What it means
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.
Source
Thrown at codex-rs/rmcp-client/src/executor_process_transport.rs:258
async move {
let _stdin_write_permit = stdin_write_semaphore
.acquire()
.await
.map_err(io::Error::other)?;
// rmcp hands us a structured JSON-RPC message. Stdio transport on
// the wire is JSON plus one newline delimiter.
let mut bytes = to_vec(&item).map_err(io::Error::other)?;
bytes.push(b'\n');
let response = process.write(bytes).await.map_err(io::Error::other)?;
match response.status {
WriteStatus::Accepted => Ok(()),
WriteStatus::UnknownProcess => {
Err(io::Error::new(io::ErrorKind::BrokenPipe, "unknown process"))
}
WriteStatus::StdinClosed => {
Err(io::Error::new(io::ErrorKind::BrokenPipe, "stdin closed"))
}
WriteStatus::Starting => Err(io::Error::new(
io::ErrorKind::WouldBlock,
"process is starting",
)),
}
}
}
fn receive(&mut self) -> impl Future<Output = Option<RxJsonRpcMessage<RoleClient>>> + Send {
self.receive_message()
}
async fn close(&mut self) -> std::result::Result<(), Self::Error> {
self.process.terminate().await.map_err(io::Error::other)?;
self.terminated = true;
Ok(())
}
}
View on GitHub (pinned to 339751715c)
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)
Example fix
// before
transport.send(message).await?; // Err(WouldBlock, 'process is starting')
// after
loop {
match transport.send(message).await {
Ok(()) => break,
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
tokio::time::sleep(std::time::Duration::from_millis(25)).await;
}
Err(e) => return Err(e.into()), // BrokenPipe etc. is terminal
}
} Defensive patterns
Strategy: retry
Type guard
fn is_process_starting(error: &std::io::Error) -> bool {
error.kind() == std::io::ErrorKind::WouldBlock
} Try / catch
// WouldBlock is transient; BrokenPipe is terminal — distinguish them
loop {
match transport.send(message).await {
Ok(()) => break,
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
tokio::time::sleep(std::time::Duration::from_millis(25)).await;
}
Err(e) => return Err(e.into()),
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- InvalidData
- current-time request failed: code={} message={}
- current-time request was canceled: {err}
- invalid requirement for MCP server `{server_name}` (set by {
- InvalidData
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/374bb75e9b79d95f.
Report an issue: GitHub.