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

  1. Retry the send on WouldBlock with a short backoff (tens of milliseconds) — the process usually becomes ready quickly
  2. If you control the flow, wait for process readiness events (Exited/Closed never arriving, first output seen) before issuing requests
  3. Do not treat this like BrokenPipe: 'unknown process'/'stdin closed' are terminal, 'process is starting' is not
  4. 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

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


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/374bb75e9b79d95f. Report an issue: GitHub.