Repository navigation
macOS: stdio MCP tool calls can fail with raw Transport closed even when the underlying operation is healthy #18977
Description
Summary
On macOS, stdio MCP servers can fail with Transport closed even when the server logic itself is healthy and the same operation succeeds via a non-MCP parity path.
What looks wrong from the outside is that Codex's MCP host/transport lifecycle seems able to drop or replace the stdio connection in a way that leaves the tool call with a stale/closed transport handle.
In my case this showed up with local OMX MCP servers (omx_state, etc.), but the evidence suggests the more general problem is on the Codex MCP host/stdio lifecycle side, not in the specific state logic.
What version of Codex is running?
codex-cli 0.122.0
What subscription do you have?
Pro
Which model were you using?
Observed inside Codex App / Codex CLI session; model choice does not seem relevant to the failure.
What platform is your computer?
macOS (Apple Silicon)
What issue are you seeing?
Intermittent MCP tool failures like:
tool call error: tool call failed for `omx_state/state_write`
Caused by:
Transport closed
Key observations:
omx doctorreports the environment is healthy.codex mcp listshows the OMX MCP servers are enabled.- The same state operations succeed via CLI parity:
omx state write ...omx state read ...
- So the state logic itself is healthy; the failure is specific to the MCP tool transport path.
I also locally verified that the OMX stdio MCP server exits immediately when stdin/transport is closed, which means Codex-side transport churn can surface directly as Transport closed.
What steps can reproduce the bug?
I do not yet have a minimal reproduction using a public MCP server, but here is the concrete local evidence path:
- Run a Codex session with stdio MCP servers configured.
- Use MCP tools successfully for a while.
- Later in the same long-running session, a tool call may fail with
Transport closed. - The same operation still succeeds immediately via a direct CLI parity command outside the MCP tool path.
Additional local debugging:
- The OMX MCP server uses
StdioServerTransportand shuts down on:stdin_endstdin_closetransport_closeparent_gone
- With
OMX_MCP_TRANSPORT_DEBUG=1, manually closing stdin reproduces a clean server shutdown with:
[omx-state-server] transport shutdown: stdin_end
[omx-state-server] transport shutdown: exit
This does not prove Codex is always at fault, but it does show that if Codex drops/replaces the stdio transport, the MCP server will terminate and the tool call will surface as Transport closed.
What is the expected behavior?
For stdio MCP servers:
- Codex should avoid exposing stale/closed transport handles to tool calls where possible.
- If the host has restarted/replaced the MCP client process, Codex should reconnect/retry rather than surfacing a raw
Transport closedimmediately. - At minimum, the error should distinguish between:
- server exited intentionally
- host transport was closed/replaced
- handshake/startup failure
- tool execution failed after a healthy connection
Why I think this is a Codex-side issue
Because the exact same logical operation succeeds through CLI parity using the same underlying handler, which strongly suggests:
- the underlying operation is fine
- the failure is in the MCP host/client transport lifecycle
Related but not identical issues
- Windows: MCP servers with heavy stderr output fail with "Transport closed" error #7155 — Windows-specific
Transport closedwith heavy stderr output - Persistent Codex MCP Support: ChromaDBserver fails, Claude Code works #6128 — persistent MCP / per-call restart behavior
This report is different: it is about macOS stdio MCP lifecycle robustness and stale transport closure surfacing as raw Transport closed during otherwise healthy local tool usage.
Additional information
If helpful, I can provide a more minimal reproduction, but I wanted to file this first because the pattern seems broader than the specific OMX tool.
Activity
github-actions commentedon Apr 22, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Transport closedwhile same stdio server works directly #18486Powered by Codex Action
alario-tang commentedon Apr 22, 2026
Additional concrete evidence from my machine:
Environment
0.122.00.14.2v25.9.026.4.1(25E253)What still works
omx doctorpasses cleanly:14 passed, 0 warnings, 0 failedomx state write --input '{"mode":"ralph","active":false,"current_phase":"complete","workingDirectory":"/Users/harrytang/Documents/GitHub/harry-study"}' --jsonomx state read --input '{"mode":"ralph","workingDirectory":"/Users/harrytang/Documents/GitHub/harry-study"}' --jsonObserved behavior in OMX's MCP bootstrap code
/opt/homebrew/lib/node_modules/oh-my-codex/dist/mcp/bootstrap.jsStdioServerTransport.stdin_end(lines 243-245)stdin_close(lines 246-248)transport_closeviatransport.onclose(lines 263-266)parent_gonewatchdog path (lines 187-193, 217-241)Minimal local proof that the MCP server exits immediately when stdio dies
OMX_MCP_TRANSPORT_DEBUG=1 node /opt/homebrew/lib/node_modules/oh-my-codex/dist/mcp/state-server.js </dev/nullOutput:
Why I think this matters on the Codex side
Transport closed.That makes this feel less like an OMX business-logic failure, and more like Codex's MCP host lifecycle needs to be more defensive around short-lived disconnects / transport replacement / child-process churn.
m13v commentedon Apr 22, 2026
hit this exact class of bug on macOS stdio MCP with a different host. the pattern that burned me worst was stderr backpressure. darwin pipes buffer ~64KB, once the host stops draining stderr (e.g. during a long tool call), the server's next log write blocks, the client's health probe trips, it replaces the transport, the old server sees transport_close from the sdk and exits cleanly. new tool call lands on the respawned child and from outside it looks exactly like yours. worth checking if codex actively drains stderr across long sessions, and whether parent_gone is PID-based since macOS recycles PIDs fast enough to false-positive during fork churn.
MarcelSimon commentedon Apr 23, 2026
I think I have a minimal non-OMX repro for this same underlying issue, plus a focused local fix/validation path.
The key difference from the original report is that this repro does not require OMX. It uses a tiny standalone stdio MCP server that returns one successful tool result, then exits. Installed Codex then reuses the stale stdio client and the next tool call fails with
Transport closed.The same local fix also resolved the OMX MCP failures I originally observed (
omx_trace,omx_state, andomx_memorycalls returningTransport closed), so the minimal repro below appears to capture the shared underlying stdio client lifecycle problem.Minimal non-OMX repro
Create this temporary MCP server:
/tmp/minimal_stdio_mcp_exit_after_first_call.pyThen run:
Observed behavior on installed Codex
Version tested locally:
codex-cli 0.122.0.The first MCP tool call succeeds:
After
sleep 0.2, the second MCP call fails in the same session:This happens almost immediately in a fresh session, not only in a long-running session: launch, first MCP call, short sleep, second MCP call.
Local fix direction I validated
The behavior appears to be in the shared stdio RMCP client lifecycle rather than in a specific MCP server. A fresh stdio client can call the server successfully, but the cached client remains in a closed state after the server exits.
Likely affected area:
codex-rs/rmcp-client/src/rmcp_client.rsI tested a focused local change that treats
rmcp::service::ServiceError::TransportClosedas a recoverable disconnect and reuses the existing reinitialize-and-retry path for one retry.Validation from local patched Codex:
cargo test -p codex-rmcp-client stdio_transport_closed_recovers_and_retries_onceTransport closedTransport closedfailures for directomx_trace,omx_state, andomx_memorytool calls in my environmentSuggested acceptance criteria
If this aligns with the intended MCP client lifecycle design, I can open a focused PR with the minimal fix and regression test.