Repository navigation
Desktop/app-server history replay omits persisted commandExecution items #28162
Description
Activity
- addedbugSomething isn't workingSomething isn't workingappIssues related to the Codex desktop appIssues related to the Codex desktop appapp-serverIssues involving app server protocol or interfacesIssues involving app server protocol or interfacessessionIssues involving session (thread) management, resuming, forking, naming, archivingIssues involving session (thread) management, resuming, forking, naming, archiving
on Jun 14, 2026 I took a look at the current replay reducer. The missing-command-history behavior seems to split into two cases.
For newer rollout rows that contain
event_msgvalues, currentmainalready has a replay path:thread/read includeTurnsloads rollout history and callsbuild_api_turns_from_rollout_items(...)ThreadHistoryBuilder::handle_event(...)handlesEventMsg::ExecCommandBeginandEventMsg::ExecCommandEndhandle_exec_command_end(...)callsbuild_command_execution_end_item(...), which maps the core event intoThreadItem::CommandExecutionwithaggregated_output,exit_code, andduration_ms- there are existing tests in
app-server-protocol/src/protocol/thread_history.rsasserting that anExecCommandEndEventreplays into a completedCommandExecutionitem
The gap I do see is the legacy-only shape mentioned in the report: rollout rows that have only
response_item.function_call(name = "exec_command")plus matchingresponse_item.function_call_output, without the newerEventMsg::ExecCommandBegin/Endrecords. In currentThreadHistoryBuilder::handle_response_item(...), response-item replay only handles userMessagerows that parse as hook prompts. Non-message response items, includingfunction_callandfunction_call_output, return early and do not contribute anyThreadItem.That explains why older sessions with only persisted Responses API tool-call rows can replay with
commandExecution: 0even though the raw JSONL still contains command calls and outputs.A minimal fix path would probably be:
- Keep the existing
EventMsg::ExecCommand*replay as the canonical path for newer rollouts. - Add a legacy fallback in
ThreadHistoryBuilderthat pairsResponseItem::FunctionCall { name: "exec_command", call_id, arguments, ... }withResponseItem::FunctionCallOutput { call_id, output }. - Reconstruct a best-effort
ThreadItem::CommandExecutionfrom those paired rows, using the parsed command/cwd from the arguments when available and the function-call output asaggregated_output. - Avoid duplicate items if both legacy response rows and newer
ExecCommandEndevent rows exist for the samecall_id; the event row should win because it carries richer status, cwd, parsed command actions, exit code, and duration.
A focused regression test could build rollout items containing only the legacy
response_itempair for anexec_commandcall and assertbuild_turns_from_rollout_items(...)returns oneThreadItem::CommandExecution. A second test should include both the legacy pair and anExecCommandEndEventfor the samecall_idand assert only the event-derived command item survives.Per
docs/contributing.md, I am not opening an unsolicited PR, but this looks like a contained replay-normalization fix if maintainers want it.VincentAdamNemessisX commented
on Jun 17, 2026 AuthorMore actionsI took a look at the current replay reducer. The missing-command-history behavior seems to split into two cases.
For newer rollout rows that contain
event_msgvalues, currentmainalready has a replay path:thread/read includeTurnsloads rollout history and callsbuild_api_turns_from_rollout_items(...)ThreadHistoryBuilder::handle_event(...)handlesEventMsg::ExecCommandBeginandEventMsg::ExecCommandEndhandle_exec_command_end(...)callsbuild_command_execution_end_item(...), which maps the core event intoThreadItem::CommandExecutionwithaggregated_output,exit_code, andduration_ms- there are existing tests in
app-server-protocol/src/protocol/thread_history.rsasserting that anExecCommandEndEventreplays into a completedCommandExecutionitem
The gap I do see is the legacy-only shape mentioned in the report: rollout rows that have only
response_item.function_call(name = "exec_command")plus matchingresponse_item.function_call_output, without the newerEventMsg::ExecCommandBegin/Endrecords. In currentThreadHistoryBuilder::handle_response_item(...), response-item replay only handles userMessagerows that parse as hook prompts. Non-message response items, includingfunction_callandfunction_call_output, return early and do not contribute anyThreadItem.That explains why older sessions with only persisted Responses API tool-call rows can replay with
commandExecution: 0even though the raw JSONL still contains command calls and outputs.A minimal fix path would probably be:
- Keep the existing
EventMsg::ExecCommand*replay as the canonical path for newer rollouts. - Add a legacy fallback in
ThreadHistoryBuilderthat pairsResponseItem::FunctionCall { name: "exec_command", call_id, arguments, ... }withResponseItem::FunctionCallOutput { call_id, output }. - Reconstruct a best-effort
ThreadItem::CommandExecutionfrom those paired rows, using the parsed command/cwd from the arguments when available and the function-call output asaggregated_output. - Avoid duplicate items if both legacy response rows and newer
ExecCommandEndevent rows exist for the samecall_id; the event row should win because it carries richer status, cwd, parsed command actions, exit code, and duration.
A focused regression test could build rollout items containing only the legacy
response_itempair for anexec_commandcall and assertbuild_turns_from_rollout_items(...)returns oneThreadItem::CommandExecution. A second test should include both the legacy pair and anExecCommandEndEventfor the samecall_idand assert only the event-derived command item survives.Per
docs/contributing.md, I am not opening an unsolicited PR, but this looks like a contained replay-normalization fix if maintainers want it.Okay, thanks for your reply.
- marked Command execution cards disappear after quitting and reopening Codex App #29850 as a duplicate of this issue
on Jun 24, 2026
Summary
Codex Desktop/app-server history replay appears to omit persisted command execution items after a session is reloaded. The raw rollout JSONL still contains the command tool calls and outputs, but
thread/readwithincludeTurns: truereturns nocommandExecutionitems for the same thread.Environment
codex-cli 0.133.0thread/readwithincludeTurns: trueEvidence
response_itempayloads withtype: "function_call"andname: "exec_command", matchingfunction_call_outputpayloads, and in some sessionsevent_msgpayloads withtype: "exec_command_end".exec_commandfunction calls, 56,291 function call outputs, and 5,603exec_command_endevents.thread/read includeTurns=truereturned 130 turns with item counts such asfileChange: 250,mcpToolCall: 53,agentMessage: 757, andcommandExecution: 0.response_item.function_call(name=exec_command)rows and sessions with newerevent_msg.exec_command_endrows also replayed withcommandExecution: 0.Expected behavior
History replay should reconstruct command execution UI items, or an equivalent expandable tool-call item, from persisted rollout rows such as:
{"type":"response_item","payload":{"type":"function_call","name":"exec_command","call_id":"...","arguments":"{...}"}} {"type":"response_item","payload":{"type":"function_call_output","call_id":"...","output":"..."}} {"type":"event_msg","payload":{"type":"exec_command_end","call_id":"...","command":["/bin/zsh","-lc","..."],"cwd":"...","exit_code":0,"status":"completed"}}Actual behavior
After reload/history replay, command execution details are missing from the returned thread items and from the Desktop UI. Other history items such as assistant messages, file changes, and some MCP/tool items can still appear.
Reproduction outline
exec_commandfunction calls and matching outputs, and/orexec_command_endevents.thread/readwithincludeTurns: truefor that thread.commandExecutionitems.Notes
threads.rollout_pathvalues point to the correct files.