Codex mcp-server can hang indefinitely when command approval requires elicitation but MCP client cannot answer

Resolved 💬 2 comments Opened Feb 14, 2026 by foxytanuki Closed Aug 26, 2026
💡 Likely answer: A maintainer (etraut-openai, contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

codex-cli 0.101.0

What subscription do you have?

ChatGPT Pro (Individual)

Which model were you using?

gpt-5.3-codex

What platform is your computer?

Linux 6.8.0-90-generic x86_64 x86_64 (Ubuntu 24.04)

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

_No response_

What issue are you seeing?

When using codex mcp-server from an MCP client (Claude Code v2.1.34) that does not respond to elicitation/create (or
does not surface approval UI), tool calls can block indefinitely if a command requires approval.

This is reproducible when approval-policy and sandbox are omitted and an unsafe command is
selected (example: pipeline with sort).

Observed behavior:

  • No final tool response is returned.
  • The turn remains stuck until manual cancellation.
  • In multi-tool scenarios, one blocked approval can delay/hold completion of other finished tool

calls.

What steps can reproduce the bug?

  1. Start MCP server:
  • codex mcp-server
  1. Call MCP tool codex without explicitly setting approval-policy / sandbox.
  2. Prompt it to run:
  • echo -e 'banana\napple\ncherry' | sort
  1. Wait for tool response.

Reproducible result:

  • Request hangs (no response for long time / indefinitely).

Control:

  • Same prompt with approval-policy: "never" and sandbox: "danger-full-access" returns

immediately.

What is the expected behavior?

codex mcp-server should fail fast instead of hanging when approval cannot be completed via MCP
client.

Expected options:

  • Detect missing elicitation support during initialize and reject approval-requiring execution with a

clear error.

  • Or apply a timeout to approval elicitation and return a structured error.
  • Or provide explicit documented behavior/fallback for non-elicitation clients.

Additional information

Relevant source references:

  • No client capability gating on initialize path: codex-rs/mcp-server/src/message_processor.rs:183
  • Elicitation request callback has no timeout: codex-rs/mcp-server/src/outgoing_message.rs:42
  • Approval wait in core path is await-based (can block until decision): `codex-rs/core/src/

codex.rs:2168`

  • Unsafe classification depends on command allowlist (sort is not in allowlist): `codex-rs/shell-

command/src/command_safety/is_safe_command.rs:56`

  • In-flight tool futures are ordered; head-of-line blocking can delay later completions: `codex-rs/

core/src/codex.rs:5237`

View original on GitHub ↗

2 Comments

delltrak · 6 months ago

I investigated the source code and traced the full approval flow in the MCP server. Here's my root-cause analysis:

The hang point

When the MCP server needs command approval, it sends an elicitation/create request to the client and spawns a task that awaits the response on a oneshot::Receiver:

exec_approval.rs:118

let response = receiver.await; // hangs indefinitely if client never responds

The same pattern exists in patch_approval.rs:108.

If the MCP client doesn't support elicitation (or simply never responds), this await blocks forever — the tool call never completes and the session appears frozen.

Three contributing issues

1. No timeout on the receiver

There is no tokio::time::timeout() wrapping the receiver.await. The core already has a cancellation-aware pattern in codex_delegate.rs via await_approval_with_cancel() using tokio::select!, but the MCP server doesn't use anything similar.

2. Client capabilities are never checked

In message_processor.rs:183-206, handle_initialize() receives params.client_info but never captures params.client_capabilities. The server has no way to know whether the client supports elicitation before sending the request.

3. Inconsistent error handling between exec and patch approval

When the oneshot channel errors (e.g., client disconnects), the two handlers behave differently:

  • patch_approval.rs:111-123 — submits ReviewDecision::Denied to the core (correct fallback)
  • exec_approval.rs:121-124 — logs the error and silently returns without submitting any decision, leaving the core waiting forever

Possible fix approaches

  1. Timeout — Wrap receiver.await with tokio::time::timeout() and default to Denied on expiry (quick fix)
  2. Capability check — Store the client's elicitation capability during handle_initialize() and deny immediately if the client doesn't support it (prevents the hang entirely)
  3. Fix exec_approval error handling — On Err, submit Op::ExecApproval { decision: ReviewDecision::Denied } like patch_approval already does
  4. All three combined for a robust solution
etraut-openai contributor · 2 days ago

Note: We've announced that the mcp-server is deprecated and will be removed in a future release. We recommend using the app server interface instead. We won't be prioritizing any bug fixes in the mcp-server, so I'm going to close this issue.