bug(exec --json): abandoned items emitted with wrong status when turn ends
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.rs — handle_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
- In the cleanup drain for
running_commands, changeCommandExecutionStatus::Completed→CommandExecutionStatus::Failed. - Drain
running_mcp_tool_callson turn end, emittingItemCompletedwithMcpToolCallStatus::Failedand 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.