bug(exec --json): abandoned items emitted with wrong status when turn ends

Open 💬 0 comments Opened Mar 14, 2026 by aamodbhatt

Summary

Two bugs in codex exec --json (EventProcessorWithJsonOutput) cause the JSONL event stream to carry misleading or structurally invalid data when a turn ends with tool calls still in-flight.

Bug 1: Abandoned shell commands emitted as Completed

File: codex-rs/exec/src/event_processor_with_jsonl_output.rshandle_task_complete

When the turn ends with shell commands still running (no ExecCommandEnd received), handle_task_complete drains them and emits ItemCompleted events with:

{ "status": "completed", "exit_code": null }

This contradicts the semantics set by handle_exec_command_end, which only uses "completed" when exit_code == 0 and uses "failed" for all non-zero exits:

// handle_exec_command_end — the normal path
let status = if ev.exit_code == 0 {
    CommandExecutionStatus::Completed  // only for exit_code == 0
} else {
    CommandExecutionStatus::Failed
};

// handle_task_complete — the cleanup path (BUG)
status: CommandExecutionStatus::Completed,  // always, even with exit_code: None

A CI script that checks status == "completed" to determine success would incorrectly treat these abandoned commands as having succeeded.

Bug 2: Abandoned MCP tool calls leave ItemStarted without ItemCompleted

handle_mcp_tool_call_begin emits ItemStarted. If McpToolCallEnd never arrives before the turn ends, handle_task_complete does not drain running_mcp_tool_calls. The JSONL stream has an unmatched ItemStarted with no ItemCompleted, breaking the contract for any consumer that tracks item lifecycle.

Fix

  1. In the cleanup drain for running_commands, change CommandExecutionStatus::CompletedCommandExecutionStatus::Failed.
  2. Drain running_mcp_tool_calls on turn end, emitting ItemCompleted with McpToolCallStatus::Failed and an explanatory error message for each.

Branch with fix: aamodbhatt/codex@fix/jsonl-incomplete-items-on-turn-end

Happy to open a PR if this is a useful direction.

View original on GitHub ↗