Review-delegate MCP tool calls hang forever: approval elicitation is unanswerable in delegated sub-agents (forward_events has no ElicitationRequest arm)
What issue are you seeing?
Under codex exec review (and any review flow that delegates to a sub-Codex), the first call to an MCP tool that requires approval is announced in the transcript (mcp: <server>/<tool> started) but never dispatched: the JSON-RPC tools/call frame is never written to the server process's stdin, and codex waits indefinitely — the 300s default tool_timeout_sec never fires (observed 23 min on 0.142.5 and 15+ min on 0.143.0 before manual kill). Plain codex exec and interactive Codex dispatch the same tool against the same server fine (review-flow calls 0/5, elsewhere 16/16 on the same machine/server/day).
Because unannotated tools default to approval-required, this hits any local stdio MCP server whose tools don't declare annotations — which is most of them — on the first tool call of every review run.
Versions: codex-cli 0.142.5 and 0.143.0 (Homebrew cask), macOS aarch64 (Mac OS 26.5.2), model gpt-5.5. Code references below are at current main (f1affbac); the mechanism is unchanged since at least rust-v0.142.5. codex doctor clean (runtime brew 0.143.0, install consistent).
Root cause
Chain verified by a trace-logged repro and a discriminating config experiment on the stock binary:
- The review task runs as a delegated one-shot sub-Codex with
approval_policypinned toNever(core/src/tasks/review.rs:118, viacodex_delegate.rs). - MCP tools without annotations default to approval-required —
requires_mcp_tool_approval(None)treats missingdestructive_hint/open_world_hintas destructive/open-world (core/src/mcp_tool_call.rs:2163). mcp_permission_prompt_is_auto_approveddoes not auto-approve: policy isNeverbut the sandbox is not full-disk-write (codex-mcp/src/mcp/mod.rs:79).- The in-session guardian route is off: it requires
OnRequest/Granular, and the delegate is pinned toNever(core/src/guardian/review.rs:173). tool_call_mcp_elicitation(stable, default-enabled) sends the approval asEventMsg::ElicitationRequestand awaits a oneshot with no timeout:rx_response.awaitinrequest_mcp_server_elicitation(core/src/session/mcp.rs:280).forward_eventsincore/src/codex_delegate.rsintercepts and answersExecApprovalRequest,ApplyPatchApprovalRequest,RequestPermissions, andRequestUserInputfrom the delegate — but has no arm forElicitationRequest; it falls through the catch-all as a display event. The gap is acknowledged in the code (codex_delegate.rs:731): "TODO(ccunningham): Support delegated MCP approval elicitations here too … Today guardian only auto-reviews the RequestUserInput compatibility path for delegated MCP approvals." One-shot delegates additionally return a closed ops channel (codex_delegate.rs:262-266), so noOp::ResolveElicitationcan ever reach the delegate session.McpToolCallBeginis emitted before the approval gate (mcp_tool_call.rs:227vs:236), so the transcript says "started".tool_timeout_secwraps only the rmcp service operation insideexecute_mcp_tool_call, downstream of the approval gate — it never starts, so it never fires.
Why plain codex exec works: the exec main session runs approval: on-request with the auto-review guardian, so step 4 routes the approval to the guardian in-session and the call proceeds.
What steps can reproduce the bug?
- Register a local stdio MCP server whose tools carry no annotations, e.g.:
``toml``
[mcp_servers.myserver]
command = "node"
args = ["my-mcp-server.js"]
- Run a review that calls one of its tools:
````
codex exec review "Review commit HEAD. Additionally, call the <tool> tool via MCP."
- Within ~a minute the transcript shows
mcp: myserver/<tool> startedand never completes; the server child's stdin has received exactly the initialize handshake +tools/listand nothing more.
Discriminating experiment (stock 0.143.0, no code changes): the same prompt wedges with default config (5/5 across 0.142.5–0.143.0) and completes in seconds when the approval gate is short-circuited with -c 'mcp_servers.<name>.default_tools_approval_mode="approve"'. A trace-logged wedge run (RUST_LOG=codex_mcp=trace,rmcp=trace,...) shows both the main and delegate sessions completing initialize+tools/list, the delegate's FunctionCall, the begin event — and zero rmcp trace activity after it: the call never reaches the rmcp client. Byte-level: live inspector autopsy of both spawned server processes of a wedged run — stdin bytesRead = initialize handshake + tools/list exactly, streams flowing and healthy, zero bytes after the handshake; children idle in kevent; the delegate rollout ends at the function_call entry with no output.
Annotating the server's tools (readOnlyHint: true, or destructiveHint: false + openWorldHint: false) also makes the wedge disappear, confirming the approval gate as the choke point — but servers shouldn't have to annotate to avoid an infinite hang.
What is the expected behavior?
A review-delegate MCP approval surfaces somewhere answerable (guardian or user) or resolves per policy — and tool_timeout_sec (or any bound) limits the wait in every case. Two defects as read: (1) delegated sub-agents emit MCP approval elicitations that nothing can answer; (2) an announced tool call can wait forever with no timeout.
Additional information — suggested minimal fix (patch available)
In run_codex_thread_interactive, disable Feature::ToolCallMcpElicitation on the delegate config, pinning delegated MCP approvals to the RequestUserInput compatibility path that forward_events already answers (guardian auto-review under exec; the parent's real user in interactive flows) — removable once the elicitation TODO is implemented. Regression test included: a review delegate calling an approval-requiring MCP tool must surface an answerable prompt on the parent session and complete once answered (it times out at the approval wait on unpatched source, passes with the pin).
A working implementation of exactly this shape — the pin, the regression test, and a managed-features caveat handled (an admin pin that force-enables the feature is kept and warned about) — is public at https://github.com/martijnwalraven/codex/tree/fix-delegated-mcp-approval-hang, shared as a fix outline per the contribution policy; happy to file it as a PR if invited. Caveats: the exec-review path is live-proven (patched binary runs the wedging repro to completion); the interactive-TUI-review path is analyzed at source but not live-tested; the pin applies to all delegated sub-agents, and a review-task-scoped variant is the more conservative alternative.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗