Repository navigation
CLI session loses stdio MCP connections after initial successful calls; fresh codex exec still works #16899
Description
Summary
In a long-lived Codex CLI session, stdio MCP servers can work initially and then permanently degrade to Transport closed for the rest of that session.
The key detail is that the servers themselves still appear healthy: repeated fresh codex exec subprocesses on the same machine continue to use the same MCP servers successfully while the original interactive session remains broken.
I reproduced this on 2026-04-06 with:
- Codex CLI
0.118.0 - Linux / WSL2:
Linux SimonRy9 6.6.87.2-microsoft-standard-WSL2 x86_64 - MCP servers:
vault_searchandrepo_search(both stdio Python servers)
Reproduction
- Start a fresh interactive Codex CLI session.
- Run a first burst of MCP calls in the session. In my case these all succeeded:
vault_search.vault_coveragerepo_search.repo_coveragevault_search.vault_search(query="Prompting")repo_search.repo_search(query="Prompting")
- Run a second burst of MCP calls in the same session.
- Observe that both MCP servers now fail with
Transport closed. - Try even simpler MCP operations like resource listing in the same session.
- Observe that those fail too.
- In parallel, run fresh
codex execsubprocesses that use the same MCP servers. - Observe that the fresh subprocesses continue to succeed repeatedly.
Expected
If the MCP servers are healthy, the existing interactive session should continue to use them successfully, or Codex should automatically reconnect / recover the MCP transport.
Actual
The interactive session enters a persistent broken state for MCP usage:
vault_search.vault_coverage->Transport closedrepo_search.repo_search(...)->Transport closedlist_mcp_resources(server="vault_search")->Transport closedlist_mcp_resources(server="repo_search")->Transport closed
At the same time, fresh codex exec subprocesses on the same machine still succeed with the same servers.
That suggests the problem is in the MCP client / session connection state of the long-lived interactive session, not in the MCP server process itself.
Why this seems like a Codex-side session bug
- Two different stdio MCP servers fail together in the same session.
- The failures persist for the remainder of that session.
- Fresh
codex execsubprocesses repeatedly succeed against the same servers. - No server config changes were made between the successful and failing calls.
Additional notes
- The same pattern reproduced again after restarting the Codex session: first MCP burst succeeded, second burst failed.
- This was not limited to one server or one MCP tool. It also affected
resources/list. - I also ran repeated fresh-process probes over time and they stayed green while the interactive session stayed broken.
Fresh-process control probe
I ran repeated fresh-process probes that call:
vault_coveragerepo_searchwith queryPrompting
All control probes succeeded while the interactive session remained broken.
Example aggregate result from the local probe log:
7/7successful fresh-process MCP runs- max duration about
25.449s - classifications: only
ok
Suggested investigation areas
- MCP transport lifecycle in long-lived interactive CLI sessions
- stdio MCP connection reuse across multiple tool bursts
- whether one failed/closed connection poisons other MCP client handles in the same session
- whether MCP resource-list calls and tool calls share a broken session-level connection pool
Activity
github-actions commentedon Apr 6, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Transport closedwhile Codex CLI succeeds on the same machine #15365Powered by Codex Action
simfor99 commentedon Apr 6, 2026
Validated a local workaround on 2026-04-06.
Workaround:
mcp__vault_search__.../mcp__repo_search__...Local wrapper paths:
scripts/mcp-searchscripts/mcp_search_workaround.pyscripts/debug/mcp_workaround_probe.pyWhat this does:
Validation:
vault_search,repo_search,vault_coverage,repo_coverage12/12successful runs,0failures5.791sRelevant local log:
tmp/logs/mcp-workaround-probe.jsonlThis does not fix the Codex bug itself, but it does confirm that the underlying search implementations remain healthy and usable while the interactive session MCP transport is degraded to
Transport closed.simfor99 commentedon Apr 6, 2026
I reviewed the suggested duplicates.
My take:
Transport closedwhile Codex CLI succeeds on the same machine #15365 does not look like a duplicate: that one is macOS desktop Browser MCP failing on brand new threads, while this report is about Codex CLI and two non-browser stdio MCP servers that work initially and then fail later in the same live session.Transport closedstate in an already-working CLI session.I am leaving this issue open for now because the distinguishing details here are:
vault_searchandrepo_search) fail togetherTransport closedcodex execcontrol probes remain healthy on the same machineIf maintainers believe #15508 already fully covers the CLI/session-transport variant too, I am happy to close this as a duplicate of #15508.
brendannoble commentedon Apr 12, 2026
I’m seeing a similar pattern here with
context-modeas the stdio MCP server.What I observed locally:
context-modeis configured as a stdio MCP server in my Codex configcontext-mode --versionreportsv1.0.75ctx_batch_executeMCP tool call failed with:tool call failed for context-mode/ctx_batch_executeCaused by: Transport closedThe main reason I’m adding this here is that it does not look like a pure initialize/handshake failure. In my case, the MCP had already worked earlier in the session, and then a later tool call failed with
Transport closed.That feels directionally similar to the failure mode described in this issue: a long-lived interactive session gets into a bad MCP transport state even though the problem is not just “server never started.”
I’m also looking at a potentially related
context-modeissue on their side:I’m not claiming root cause yet, only that I’m seeing the same client-visible symptom class from Codex:
Transport closedsimfor99 commentedon Apr 22, 2026
Still reproducible on 2026-04-22 with
codex-cli 0.122.0on Linux/WSL2.I hit the same failure mode again today with our stdio Python MCPs, especially
vault_search(and alsorepo_searchin the same session):Transport closedCurrent evidence from
~/.codex/log/codex-tui.logon 2026-04-22:read_mcp_resource {"server":"vault_search","uri":"config://info"}->Transport closedlist_mcp_resources {"server":"vault_search"}->Transport closedmcp__vault_search__vault_search {...}->Transport closedrepo_searchMCP calls failing withTransport closedFresh-process control probe on the same machine is still green:
codex execsubprocess today and asked it to callvault_search.vault_coverageexactly onceRESULT: OKSo
#16899does not appear to be fixed in0.122.0. This still looks like a Codex-side MCP transport/session lifecycle bug in long-lived interactive CLI sessions, not a bug in the MCP server implementation itself.A short-term mitigation might be to route affected FastMCP servers over local Streamable HTTP instead of stdio, but that is only a workaround. The actual product fix still seems to be needed on the Codex side: the CLI should either keep these MCP transports healthy across turns or automatically reconnect/recover instead of poisoning the rest of the session after the first break.
Please prioritize a real fix here. For MCP-heavy workflows, the current behavior makes the interactive CLI unreliable.
simfor99 commentedon Apr 22, 2026
Follow-up with a working local mitigation / workaround.
I was able to stop the recurring MCP breakage for our local Python MCPs by moving them off
stdioand onto local Streamable HTTP endpoints.What worked here on 2026-04-22:
transport="streamable-http"instead ofstdio.--userin my case).command/argsin~/.codex/config.toml.Example config pattern:
Example service pattern:
Results on my side after switching to local Streamable HTTP:
vault_search: stable, verified with repeated live searches in fresh Codex sessionswindows-search: stable, verified with live searchesrepo_search: MCP transport is stable andrepo_coverageworks; I still see a separate search/index-recall issue on my current repo index, but that appears independent from the Codex MCP transport problemSo the original Codex bug still matters: long-lived
stdioMCP sessions were the thing poisoning the interactive CLI for us. But if someone is blocked right now and their server already supports FastMCP HTTP transport, switching the local MCP fromstdioto Streamable HTTP is a practical workaround.simfor99 commentedon Apr 22, 2026
Follow-up after applying the workaround locally.
I was able to get all three local Python MCPs (
vault_search,repo_search,windows-search) working reliably in Codex by moving them fromstdioto local Streamable HTTP endpoints and running them as long-lived user services.What I changed:
--transport streamable-http --host 127.0.0.1 --port <port> --streamable-http-path /mcp--useron my machine)url = "http://127.0.0.1:<port>/mcp"instead ofcommand/argstool_timeout_secfor heavier MCPs and increasestartup_timeout_secforrepo_searchExample
config.tomlpattern:One extra bug I found while fixing
repo_search:repo_searchlook superficially healthy while every real search returned no hitsrepo_searchimplementation to detectmetadata exists but table missingand force a rebuild instead of incorrectly returningup_to_dateCurrent result on my side:
vault_search: working with real searcheswindows-search: working with real searchesrepo_search: working again with real docs/code searches after the rebuild fixSo the product bug still matters: Codex interactive sessions and local
stdioMCPs were not stable enough for this workflow. But for anyone blocked today, local Streamable HTTP MCP + a persistent local service is a viable workaround.welf commentedon Jul 25, 2026
I investigated this issue and have a tested fix ready.
TLDR
Root cause:
RmcpClientretains a closedRunningServiceinClientState::Ready, so subsequent operations reuse the dead transport indefinitely. Fresh Codex processes work because they construct a new client and transport.The proposed fix recreates and initializes the transport behind the existing recovery semaphore, shares one recovery across concurrent callers, never replays an interrupted tool call, preserves HTTP 404 retry behavior, and tracks replacement stdio processes for cleanup.
Validation: all 119
codex-rmcp-clienttests pass, including new preclosed, concurrent recovery, no-replay, and process-cleanup regressions.Tested commit: welf@bba585c
Would a Codex maintainer be willing to invite a PR for this approach?
Here is the full PR description:
Fixes #16899
Root cause
RmcpClientstores a successfully initialized service inClientState::Ready. When the RMCP service task or underlyingtransport closes, the stored state is not changed. Normal operations retrieve that
Readyservice without checkingwhether it is still alive.
The RMCP dependency returns
rmcp::service::ServiceError::TransportClosed, whose display text isTransport closed.Codex returns that error but leaves the same closed service cached.
Prepared tool calls and resource clients retain the same
Arc<RmcpClient>. Since transport closure does not mark theMCP runtime dirty, the ordinary per-step dirty check does not rebuild the connection. Every subsequent operation
therefore calls the same closed service.
A new Codex process constructs a new
McpRuntime,McpConnectionSet,ManagedClient, andRmcpClient, which explainswhy fresh
codex execprocesses continue to work.What
Recover an MCP client when its current transport has closed instead of leaving a long-lived Codex session permanently
attached to a dead
RunningService.TransportClosed, but return the original error and never replaythe operation.
Why
A persistent CLI session kept the original closed MCP service after a stdio server or transport exited. Every later tool
call therefore failed with
Transport closed, while a freshcodex execworked because it constructed a new client andtransport.
How
The client now uses its retained transport recipe and initialization context to rebuild the service behind the existing
recovery semaphore. Recovery atomically installs the replacement service, OAuth persistor, and stdio process handle.
Callers that arrive concurrently reuse a healthy replacement installed by the first recovery attempt.
Regression coverage verifies pre-closed recovery, single recovery under concurrency, no replay after an in-flight
tool-call closure, replacement stdio cleanup, and unchanged HTTP 404 retry semantics. The new recovery regressions were
confirmed to fail before the implementation.
Validation
just test -p codex-rmcp-client— 119 passed, 5 skippedjust argument-comment-lint -p codex-rmcp-clientjust fix -p codex-rmcp-clientjust fmtIssue: #16899
FlourishingHumanityCorporation commentedon Jul 26, 2026
Additional macOS/Desktop acceptance evidence from 2026-07-26:
origin/main(61a44880a85d2fd0d8770908dea5733495e571c8).node_repl/jsprobe fails immediately withTransport closed.sky.list_apps()returned 30 apps). This is a direct long-lived-session-vs-fresh-process A/B and matches the cached-dead-RunningServicediagnosis above.codex-rmcp-clienttest suite, 125codex-mcptests, Clippy, formatting, and the focused real-stdio/HTTP recovery tests. The important safety rule is unchanged: a transport known closed before dispatch reconnects and executes the next operation once; a mid-operation closure reconnects for future calls but never replays the ambiguous tool call.One product-acceptance wrinkle is worth calling out for maintainers: a locally built patched Codex binary cannot validate Computer Use end-to-end on macOS. The Sky native-pipe service accepts the official OpenAI-signed host, while the otherwise equivalent ad-hoc local build times out during the native-pipe ping. The service binary exposes sender-authentication errors and an OpenAI team-ID allowlist, so this appears to be a signed-distribution boundary, not evidence against the transport fix.
There is already a backend
config/mcpServer/reloadrequest, but the current Desktop UI does not expose an MCP-only recovery action; its MCP settings restart control restarts the whole app-server. That is not a safe workaround when other tasks are active.Per this repository's invitation-only contribution policy, I am not opening an unsolicited PR. A maintainer invitation—or an internal application of the proposed fix followed by an official signed build—is now the remaining path to product-level Computer Use acceptance.
FlourishingHumanityCorporation commentedon Jul 26, 2026
Additional discriminating evidence: while the existing Desktop task still returns
Transport closedfor bothnode_repl/jsand local Figma, a fresh instance of the official signed bundledcodex app-server --listen stdio://can recover access without starting a model turn.A minimal client performs
initialize→initialized→ ephemeral read-onlythread/start→ onemcpServer/tool/call, then terminates only that app-server. Through this route, Computer Use initialized andsky.list_apps()returned 29 apps; a separate local Figma call returned all 10 connected files. A hermetic failure probe verifies the client sends exactly onemcpServer/tool/calland does not replay after a tool error. Process inspection after the live calls found no new app-server or node_repl child residue.This strengthens the cached task-scoped MCP client diagnosis: downstream services, MCP configuration, and a fresh authenticated client remain healthy. It also suggests a temporary product-safe recovery surface could use an MCP-only client refresh rather than the current whole-app-server Restart action. The actual product fix still needs the in-place pre-dispatch reconnect plus midflight no-replay semantics described above.
ertankucukoglu commentedon Aug 17, 2026
Independent confirmation on Windows with Codex CLI 0.147.0, with an important extension: this is
not limited to resume or an already long-lived session. It can also be present immediately after
a normal TUI launch/new thread.
Environment
Observed behavior
Calls to list_projects, index_repository, search_code, and search_graph have failed with:
Caused by:
Transport closed
The failure is not limited to resumed or long-running sessions. It can also occur immediately
after a normal Codex TUI launch or a new thread.
Codex logs contain normal rpc.method="thread/start" paths—not only thread/resume—where the stdio
MCP child later reports:
rmcp::transport::child_process:
Child exited gracefully exit code: 1
Other startup traces successfully report:
Service initialized as client
server_info.name = "codebase-memory-mcp"
server_info.version = "0.10.6"
protocol_version = "2025-06-18"
The tools can remain exposed even though calls are dispatched through an unusable transport.
Controls
codex mcp list and codex mcp get codebase-memory-mcp report the server as enabled.
codex doctor --json reports overallStatus: ok and mcp.config: ok.
A direct newline-delimited JSON-RPC probe against the exact configured executable and
environment passes:
The direct probe exits with code 0.
Historical Transport closed occurrences predate the upgrade to codebase-memory-mcp 0.10.6, so
this is not a 0.10.6 regression.
Expected behavior
If an MCP child or service closes during or after normal startup, Codex should recreate and
rebind the stdio client before exposing or dispatching its tools.
If recovery fails, the server should be marked unavailable instead of leaving tools backed by a
closed service in the active catalog.
The existing closed-service diagnosis still matches the persistent failure mode. The additional
observation is that a normal startup or new thread can also enter this state; resume is not
required.
It would also help if codex doctor optionally performed a live initialize and tools/list health
check, because its current MCP configuration check can report ok while the active tool transport
is unusable.
ertankucukoglu commentedon Aug 26, 2026
Follow-up from the same Windows environment: reproduced again on 2026-08-26 with newer versions.
Environment
0.149.110.0.26200, x64codebase-memory-mcp 0.10.7Observed behavior
The configured MCP tool remained exposed, but repeated calls failed immediately:
Retrying the same MCP tool in the existing Codex session did not respawn or rebind the stdio server. Starting a temporary daemon through the server's direct CLI also did not repair the stale Codex-side tool binding; the next Codex MCP call still failed with
Transport closed.Controls
The MCP binary and its data are healthy:
codebase-memory-mcp --versionexits successfully and reports0.10.7.codebase-memory-mcp cli --json list_projects ...succeeds and returns 377 indexed projects.After the direct CLI control succeeded, the original Codex session remained permanently attached to the closed transport.
This is another confirmation of the cached closed
RunningService/ missing reconnect behavior described in this issue and in #35486, now on Codex CLI0.149.1.