MCP re-init loop on Stop-hook path hangs `codex exec` until timeout when an MCP server fails to start (effort-correlated)
What version of Codex CLI is running?
codex-cli 0.144.1
What subscription do you have?
ChatGPT (auth_mode: Chatgpt)
Which model were you using?
gpt-5.6-sol (also reproduces model-agnostically; the trigger is reasoning effort, not the model)
What platform is your computer?
Windows 11 Pro 10.0.26200 (running under Git Bash / MSYS). Node v24.13.1.
What issue are you seeing?
When a Stop hook is registered and at least one configured MCP server fails to start (e.g. a stdio server that exits non-zero at launch, or an HTTP server that needs auth), running codex exec with -c model_reasoning_effort=ultra causes Codex to hang after the model response is already printed. The model answers correctly, then Codex spins in a MCP re-initialization retry loop during shutdown and never exits — it is only killed by an external timeout (exit 124).
The observable surface is a burst of hook: Stop / hook: Stop Failed lines, but the hook scripts are not the cause (see isolation below). The real loop is Codex re-initializing the entire MCP manager on the Stop-hook path and retrying the failing server(s) indefinitely.
With RUST_LOG=info, the shutdown phase repeats this sequence dozens of times:
hook: Stop
...
WARN codex_mcp::rmcp_client: failed to initialize MCP client during shutdown:
MCP startup failed: handshaking with MCP server failed:
JSON-RPC error: -32600: Vault directory does not exist: c:/jexer/vault
WARN codex_mcp::rmcp_client: failed to initialize MCP client during shutdown:
... Auth required, when send initialize request
app_server.request{... thread/start ...}: session_init.mcp_manager_init: ...
rmcp::transport::child_process: Child exited gracefully exit code: 1
rmcp::service: input stream terminated ... serve finished quit_reason=Closed
thread/start → session_init.mcp_manager_init fires 50+ times and Child exited exit code: 1 8–9 times in a single codex exec shutdown.
Reproduction
- Register any Stop hook (content is irrelevant — even a script that immediately
exit(0)triggers it). - Have at least one configured MCP server that fails at startup. Two real examples we hit:
- stdio:
npx -y obsidian-mcp c:/jexer/vaultwhere the vault dir does not exist →Error: Vault directory does not exist. - HTTP:
https://mcp.figma.com/mcpwhile not logged in →Auth required.
- Run with high reasoning effort so the turn takes a while:
timeout 70 codex exec -m gpt-5.6-sol -c model_reasoning_effort=ultra \
-s read-only "Reply with exactly: PONG-ULTRA"
Result: PONG-ULTRA prints, then the Stop-hook / MCP-reinit loop runs until the 70s timeout → exit 124. With a short/medium turn the loop sometimes finishes before the timeout, so the failure is intermittent and effort-correlated.
Isolation (what is and isn't the cause)
Three controlled runs, using exited gracefully exit code: 1 count as the loop indicator:
| Change | loop count | conclusion |
|---|---|---|
| Disable the plugin providing the Stop hooks (no Stop hook) | 0 | a registered Stop hook is the trigger |
| Keep Stop hooks, replace the hook scripts with process.exit(0) stubs | 9 | hook script content / any subprocess it spawns is not the cause |
| Keep Stop hooks, create the missing vault dir (fix one failing MCP) | 8 (only the auth one remains) | it's the failing MCP, and each failing server contributes |
Removing the failing MCP servers eliminates the loop entirely (verified: 4/4 ultra runs exit 0, zero Vault directory / Auth required in logs).
Cross-agent control
We ran the same failing stdio MCP under a different harness (Claude Code, --mcp-config with a stub that exits 1 at startup, same Stop hooks firing). It logged the identical startup failure (Connection failed ... MCP error -32000) but did not retry — it failed once and exited 0 in ~33s. So this is specific to how Codex re-initializes the MCP manager on the Stop-hook / shutdown path, not a generic MCP-startup problem.
Expected behavior
A failing MCP server should fail once (or with bounded retries) and must not block session/process termination. The Stop-hook path should not re-initialize the whole MCP manager and retry unhealthy servers unboundedly. codex exec should exit promptly after the model response regardless of a dead MCP server or reasoning effort.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗