First-turn API call blocks until pending MCP tools/list times out

Open 💬 3 comments Opened Apr 25, 2026 by eyupcanakman

What version of Codex CLI is running?

codex-cli 0.124.0 (also reproduced on 0.123.0; symptoms reported as far back as 0.114.0 in #14470, #14627)

What subscription do you have?

Pro

Which model were you using?

gpt-5.5 with reasoning xhigh

What platform is your computer?

macOS 26.5, arm64

What terminal emulator and version are you using (if applicable)?

Terminal CLI session

What issue are you seeing?

When any configured MCP server stalls on tools/list (or completes initialize slowly), the first turn after launch or session resume sits idle for the full startup_timeout_sec (default 30s) before any model API call goes out. The TUI shows Working (Xs) for the whole window without saying what it is waiting on. Ctrl+C during the wait aborts the turn with no assistant output.

In the original session that triggered this report (codex-cli 0.123.0, 4 newly added MCPs in ~/.codex/config.toml), the wait stretched to ~340s and ~470s on consecutive retries before I gave up and closed the session. The 0.124.0 wait is bounded by the 30s timeout but the same code path stalls every first turn.

What steps can reproduce the bug?

Save this as tools_list_hang_mcp_server.py (zero deps, stdlib only):

import json, sys
for raw in sys.stdin:
    line = raw.strip()
    if not line:
        continue
    req = json.loads(line)
    method = req.get("method")
    if method == "initialize":
        print(json.dumps({
            "jsonrpc": "2.0",
            "id": req["id"],
            "result": {
                "protocolVersion": "2024-11-05",
                "capabilities": {"tools": {"listChanged": False}},
                "serverInfo": {"name": "hang", "version": "0.1.0"},
            },
        }), flush=True)
    # tools/list and everything else: no response

Then resume any prior session with this server attached and submit a prompt:

codex resume <session-id> \
  -c 'mcp_servers.hang.command="python3"' \
  -c 'mcp_servers.hang.args=["/abs/path/tools_list_hang_mcp_server.py"]'
> Reply with OK.

A fresh session (codex with the same -c overrides) reproduces it too.

What is the expected behavior?

The first turn dispatches the API call without waiting on tools/list from every server. A slow MCP's tools become available on the next turn once its startup completes or times out.

Additional information

Rollout for the resumed turn shows the gap concretely:

L547  task_started   2026-04-25T11:37:53.449Z
L548  user msg       2026-04-25T11:38:04.592Z   (+11.1s)
L549  turn_context   2026-04-25T11:38:04.593Z
L550  user prompt    2026-04-25T11:38:20.234Z   (+15.6s)
L551  user_message event
L552  token_count    2026-04-25T11:38:20.895Z   (API call dispatched, +27.4s)
L553  agent_message  2026-04-25T11:38:25.580Z

A normal turn on the same session without the hanging MCP takes task_started to token_count under 700ms.

After the timeout fires the TUI shows:

⚠ MCP client for `hang` failed to start: MCP startup failed:
  timed out awaiting tools/list after 30s

That message arrives 30 seconds after the user submitted, with Working shown the whole time.

Root cause is in codex-rs/codex-mcp/src/mcp_connection_manager.rs:

  • AsyncManagedClient::listed_tools() (around L630) awaits self.client() for any server without a startup_snapshot, blocking on the full per-server startup future.
  • McpConnectionManager::list_all_tools() (L925) iterates every client and awaits each in turn, so one stalled server gates the whole map.
  • Session::run_turn calls list_all_tools() (codex-rs/core/src/session/turn.rs:190) before the API call.
  • The existing test list_all_tools_blocks_while_client_is_pending_without_startup_snapshot (mcp_connection_manager_tests.rs:687) asserts the blocking behavior, so the fix is a contract change to that test rather than a one-line patch.

The codex_apps server already has a startup_snapshot cache via load_startup_cached_codex_apps_tools_snapshot, so it is not affected. User-defined MCPs always go through the blocking path on first turn.

Possibly the same root cause behind older reports that lacked a deterministic repro: #14470, #14627, #11015. Each describes a silent first-turn hang with MCP servers active but never produced a reliable trigger.

I have a working patch (skip clients with startup_complete == false in listed_tools(), fall back to existing startup_snapshot) plus updated tests. Happy to open a PR if invited.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗