Skip to content

MCP server never emits TurnComplete event - only threadId returned to caller #9251

@ryuan-cw

Description

@ryuan-cw

Bug Description

When using the Codex MCP server (codex mcp-server), tool calls return only a threadId in the response instead of the actual GPT response text. Session logs confirm GPT responds correctly with agent_message events, but the TurnComplete event that would surface this response is never emitted.

Steps to Reproduce

  1. Start Codex as an MCP server:

    codex mcp-server
  2. Call the codex tool with any prompt via an MCP client (e.g., Claude Code)

  3. Observe the response contains only:

    {"threadId": "019bbed6-1e9e-7f31-984c-a05b65045719"}
  4. Check session logs at ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl - the agent_message with the actual response is present, but no turn_complete event follows.

Expected Behavior

The MCP tool call should return the GPT response text along with the threadId:

{
  "content": [{"type": "text", "text": "GPT's response here"}],
  "structured_content": {"threadId": "..."}
}

Actual Behavior

Only the threadId is returned. The response text is lost.

Technical Analysis

Event Flow

MCP Client → codex mcp-server → Codex Core → GPT API
                    ↓
         codex_tool_runner.rs waits for TurnComplete
                    ↓
         (event never arrives, only threadId returned)

Code Path

  1. MCP server correctly waits for TurnComplete in codex-rs/mcp-server/src/codex_tool_runner.rs:

    EventMsg::TurnComplete(TurnCompleteEvent { last_agent_message }) => {
        let text = match last_agent_message { ... };
        let result = create_call_tool_result_with_thread_id(thread_id, text, None);
        outgoing.send_response(request_id.clone(), result).await;
        break;
    }
  2. TurnComplete is only emitted from Session::on_task_finished() in codex-rs/core/src/tasks/mod.rs:

    pub async fn on_task_finished(...) {
        let event = EventMsg::TurnComplete(TurnCompleteEvent { last_agent_message });
        self.send_event(turn_context.as_ref(), event).await;
    }
  3. on_task_finished should be called from spawn_task() closure after task_for_run.run() completes

Session Log Evidence

The session log shows the conversation completes successfully but ends without turn_complete:

{"timestamp":"2026-01-14T17:18:29.861Z","event_msg":{"agent_message":"Hello from GPT"}}
{"timestamp":"2026-01-14T17:18:29.861Z","response_item":{"type":"message"}}
{"timestamp":"2026-01-14T17:18:29.908Z","event_msg":{"token_count":{...}}}
[END - no turn_complete event]

Hypothesis

The task lifecycle in MCP server mode does not properly trigger on_task_finished(). Possible causes:

  • The task isn't being registered via spawn_task() in MCP mode
  • The task completion callback is being skipped
  • There's a race condition where the event channel closes before TurnComplete can be emitted

Environment

  • OS: macOS Darwin 25.2.0
  • Codex version: 0.81.0
  • MCP client: Claude Code

Workaround

Currently, the only workaround is to manually parse session log files to extract agent_message content, which defeats the purpose of the MCP integration.

Impact

This bug makes the Codex MCP server unusable for any application expecting to receive GPT responses, as only session IDs are returned instead of actual content.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingmcpIssues related to the use of model context protocol (MCP) serversmcp-serverIssues related to the use of the `codex mcp-server` subcommand

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions