Skip to content

CLI session loses stdio MCP connections after initial successful calls; fresh codex exec still works #16899

Open

Description

@simfor99

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_search and repo_search (both stdio Python servers)

Reproduction

  1. Start a fresh interactive Codex CLI session.
  2. Run a first burst of MCP calls in the session. In my case these all succeeded:
    • vault_search.vault_coverage
    • repo_search.repo_coverage
    • vault_search.vault_search(query="Prompting")
    • repo_search.repo_search(query="Prompting")
  3. Run a second burst of MCP calls in the same session.
  4. Observe that both MCP servers now fail with Transport closed.
  5. Try even simpler MCP operations like resource listing in the same session.
  6. Observe that those fail too.
  7. In parallel, run fresh codex exec subprocesses that use the same MCP servers.
  8. 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 closed
  • repo_search.repo_search(...) -> Transport closed
  • list_mcp_resources(server="vault_search") -> Transport closed
  • list_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 exec subprocesses 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_coverage
  • repo_search with query Prompting

All control probes succeeded while the interactive session remained broken.

Example aggregate result from the local probe log:

  • 7/7 successful 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

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    mcpIssues related to the use of model context protocol (MCP) servers
    tool-callsIssues related to tool calling
    on Apr 6, 2026
  2. simfor99 commented on Apr 6, 2026

    @simfor99
    Author

    Validated a local workaround on 2026-04-06.

    Workaround:

    • bypass the broken interactive-session MCP transport entirely
    • call the local search implementations directly via a wrapper script instead of mcp__vault_search__... / mcp__repo_search__...

    Local wrapper paths:

    • scripts/mcp-search
    • scripts/mcp_search_workaround.py
    • periodic probe: scripts/debug/mcp_workaround_probe.py

    What this does:

    • uses the Sanctum venv Python directly
    • loads the local Vault/Repo search server wrappers and calls their search / coverage functions without going through the long-lived Codex MCP session transport

    Validation:

    • 3 iterations
    • 180-second interval between iterations
    • 4 probes per iteration: vault_search, repo_search, vault_coverage, repo_coverage
    • result: 12/12 successful runs, 0 failures
    • max probe duration: 5.791s

    Relevant local log:

    • tmp/logs/mcp-workaround-probe.jsonl

    This 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.

  3. simfor99 commented on Apr 6, 2026

    @simfor99
    Author

    I reviewed the suggested duplicates.

    My take:

    I am leaving this issue open for now because the distinguishing details here are:

    • Codex CLI, not desktop/app-only
    • two separate stdio MCP servers (vault_search and repo_search) fail together
    • the first MCP burst in-session succeeds, then the session degrades to Transport closed
    • repeated fresh codex exec control probes remain healthy on the same machine

    If maintainers believe #15508 already fully covers the CLI/session-transport variant too, I am happy to close this as a duplicate of #15508.

  4. removed
    CLIIssues related to the Codex CLI
    tool-callsIssues related to tool calling
    on Apr 6, 2026
  5. brendannoble commented on Apr 12, 2026

    @brendannoble

    I’m seeing a similar pattern here with context-mode as the stdio MCP server.

    What I observed locally:

    • context-mode is configured as a stdio MCP server in my Codex config
    • context-mode --version reports v1.0.75
    • the server was not failing at startup; it was usable earlier in the same interactive Codex session
    • later in that same session, a ctx_batch_execute MCP tool call failed with:

    tool call failed for context-mode/ctx_batch_execute
    Caused by: Transport closed

    The 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-mode issue on their side:

    • mksglu/context-mode#236

    I’m not claiming root cause yet, only that I’m seeing the same client-visible symptom class from Codex:

    • stdio MCP works initially
    • later call fails with Transport closed
  6. simfor99 commented on Apr 22, 2026

    @simfor99
    Author

    Still reproducible on 2026-04-22 with codex-cli 0.122.0 on Linux/WSL2.

    I hit the same failure mode again today with our stdio Python MCPs, especially vault_search (and also repo_search in the same session):

    • in a long-lived interactive CLI session, MCP calls worked initially and then degraded to persistent Transport closed
    • once the session entered that state, even simple MCP operations like resource access/listing failed for the rest of the session
    • the server itself does not look like the root cause here: the same machine still succeeds with a fresh subprocess probe

    Current evidence from ~/.codex/log/codex-tui.log on 2026-04-22:

    • read_mcp_resource {"server":"vault_search","uri":"config://info"} -> Transport closed
    • list_mcp_resources {"server":"vault_search"} -> Transport closed
    • mcp__vault_search__vault_search {...} -> Transport closed
    • the same interactive session also later showed repo_search MCP calls failing with Transport closed

    Fresh-process control probe on the same machine is still green:

    • I ran a fresh codex exec subprocess today and asked it to call vault_search.vault_coverage exactly once
    • result: RESULT: OK

    So #16899 does not appear to be fixed in 0.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.

  7. simfor99 commented on Apr 22, 2026

    @simfor99
    Author

    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 stdio and onto local Streamable HTTP endpoints.

    What worked here on 2026-04-22:

    1. Keep the same FastMCP server implementation, but start it with transport="streamable-http" instead of stdio.
    2. Run each MCP as a long-lived local user service (systemd --user in my case).
    3. Point Codex at the local MCP URL instead of command / args in ~/.codex/config.toml.
    4. Restart the Codex session so it reloads the MCP registry.

    Example config pattern:

    [mcp_servers.vault_search]
    url = "http://127.0.0.1:8765/mcp"
    startup_timeout_sec = 20
    tool_timeout_sec = 45
    
    [mcp_servers.repo_search]
    url = "http://127.0.0.1:8766/mcp"
    startup_timeout_sec = 20
    tool_timeout_sec = 180
    
    [mcp_servers.windows-search]
    url = "http://127.0.0.1:8767/mcp"
    startup_timeout_sec = 20
    tool_timeout_sec = 180

    Example service pattern:

    [Service]
    ExecStart=/home/simon/.venvs/sanctum/bin/python3 /path/to/server.py --transport streamable-http --host 127.0.0.1 --port 8765 --streamable-http-path /mcp
    Restart=always
    RestartSec=2

    Results on my side after switching to local Streamable HTTP:

    • vault_search: stable, verified with repeated live searches in fresh Codex sessions
    • windows-search: stable, verified with live searches
    • repo_search: MCP transport is stable and repo_coverage works; I still see a separate search/index-recall issue on my current repo index, but that appears independent from the Codex MCP transport problem

    So the original Codex bug still matters: long-lived stdio MCP 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 from stdio to Streamable HTTP is a practical workaround.

  8. simfor99 commented on Apr 22, 2026

    @simfor99
    Author

    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 from stdio to local Streamable HTTP endpoints and running them as long-lived user services.

    What I changed:

    • start each FastMCP server with --transport streamable-http --host 127.0.0.1 --port <port> --streamable-http-path /mcp
    • run each one via a local user service (systemd --user on my machine)
    • point Codex at url = "http://127.0.0.1:<port>/mcp" instead of command/args
    • raise tool_timeout_sec for heavier MCPs and increase startup_timeout_sec for repo_search

    Example config.toml pattern:

    [mcp_servers.vault_search]
    url = "http://127.0.0.1:8765/mcp"
    startup_timeout_sec = 20
    tool_timeout_sec = 45
    
    [mcp_servers.repo_search]
    url = "http://127.0.0.1:8766/mcp"
    startup_timeout_sec = 60
    tool_timeout_sec = 180
    
    [mcp_servers.windows-search]
    url = "http://127.0.0.1:8767/mcp"
    startup_timeout_sec = 60
    tool_timeout_sec = 180

    One extra bug I found while fixing repo_search:

    • the repo index metadata claimed chunks existed, but the actual LanceDB table was missing
    • that made repo_search look superficially healthy while every real search returned no hits
    • I patched my local repo_search implementation to detect metadata exists but table missing and force a rebuild instead of incorrectly returning up_to_date
    • I also changed the reindex path from an all-in-memory build to batch flushing, because the old path could balloon to ~14 GB RAM and wedge the service

    Current result on my side:

    • vault_search: working with real searches
    • windows-search: working with real searches
    • repo_search: working again with real docs/code searches after the rebuild fix

    So the product bug still matters: Codex interactive sessions and local stdio MCPs were not stable enough for this workflow. But for anyone blocked today, local Streamable HTTP MCP + a persistent local service is a viable workaround.

  9. welf commented on Jul 25, 2026

    @welf

    I investigated this issue and have a tested fix ready.

    TLDR

    Root cause: RmcpClient retains a closed RunningService in ClientState::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-client tests 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

    RmcpClient stores a successfully initialized service in ClientState::Ready. When the RMCP service task or underlying
    transport closes, the stored state is not changed. Normal operations retrieve that Ready service without checking
    whether it is still alive.

    The RMCP dependency returns rmcp::service::ServiceError::TransportClosed, whose display text is Transport 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 the
    MCP 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, and RmcpClient, which explains
    why fresh codex exec processes 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.

    • Recreate and initialize the transport before the next operation when closure is already observable.
    • Serialize concurrent recovery attempts so callers share one replacement connection.
    • Recover eagerly after an in-flight operation reports TransportClosed, but return the original error and never replay
      the operation.
    • Track the active stdio process alongside the active service so shutdown cleans up a replacement server too.
    • Preserve the existing streamable HTTP 404 session-expiry behavior and its single operation retry.

    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 fresh codex exec worked because it constructed a new client and
    transport.

    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 skipped
    • just argument-comment-lint -p codex-rmcp-client
    • just fix -p codex-rmcp-client
    • just fmt

    Issue: #16899

  10. FlourishingHumanityCorporation commented on Jul 26, 2026

    @FlourishingHumanityCorporation

    Additional macOS/Desktop acceptance evidence from 2026-07-26:

    • The issue remains reproducible against current origin/main (61a44880a85d2fd0d8770908dea5733495e571c8).
    • In an existing signed Codex Desktop task, a read-only node_repl/js probe fails immediately with Transport closed.
    • On the same machine, a fresh ephemeral invocation of the official bundled Codex binary can call the same Computer Use MCP successfully (sky.list_apps() returned 30 apps). This is a direct long-lived-session-vs-fresh-process A/B and matches the cached-dead-RunningService diagnosis above.
    • The replay-safe recovery implementation has also been rebased onto that upstream head and remains green across the complete codex-rmcp-client test suite, 125 codex-mcp tests, 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/reload request, 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.

  11. FlourishingHumanityCorporation commented on Jul 26, 2026

    @FlourishingHumanityCorporation

    Additional discriminating evidence: while the existing Desktop task still returns Transport closed for both node_repl/js and local Figma, a fresh instance of the official signed bundled codex app-server --listen stdio:// can recover access without starting a model turn.

    A minimal client performs initialize → initialized → ephemeral read-only thread/start → one mcpServer/tool/call, then terminates only that app-server. Through this route, Computer Use initialized and sky.list_apps() returned 29 apps; a separate local Figma call returned all 10 connected files. A hermetic failure probe verifies the client sends exactly one mcpServer/tool/call and 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.

  12. ertankucukoglu commented on Aug 17, 2026

    @ertankucukoglu

    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

    • Codex CLI: 0.147.0
    • Model: gpt-5.6-sol
    • OS: Windows 11 Pro 10.0.26200, x64
    • Terminal: Windows Terminal / PowerShell 5.1
    • MCP transport: local stdio
    • MCP server: codebase-memory-mcp 0.10.6

    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:

      • initialize
      • notifications/initialized
      • tools/list
    • 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.

  13. ertankucukoglu commented on Aug 26, 2026

    @ertankucukoglu

    Follow-up from the same Windows environment: reproduced again on 2026-08-26 with newer versions.

    Environment

    • Codex CLI: 0.149.1
    • OS: Windows 11 Pro 10.0.26200, x64
    • MCP transport: local stdio
    • MCP server: codebase-memory-mcp 0.10.7

    Observed behavior

    The configured MCP tool remained exposed, but repeated calls failed immediately:

    tool call error: tool call failed for `codebase-memory-mcp/list_projects`
    
    Caused by:
        Transport closed
    

    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 --version exits successfully and reports 0.10.7.
    • Direct codebase-memory-mcp cli --json list_projects ... succeeds and returns 377 indexed projects.
    • The configured executable path exists and the active configuration is readable.
    • Windows Application logs contain no crash event for the MCP process.
    • The MCP daemon log shows a clean lifecycle rather than a crash:
    daemon.runtime_stopping reason=last_committed_client_disconnected
    daemon.lifetime_end reason=runtime_exited
    watcher.stop
    daemon.stop
    

    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 CLI 0.149.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmcpIssues related to the use of model context protocol (MCP) servers

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions