What happened?
Reasoning summaries can contain a literal empty HTML comment placeholder, <!-- -->. This placeholder then shows up in user-facing surfaces such as the TUI progress/history view and codex exec --json output.
Example observed in the TUI:
• <!-- -->
Clarifying Codex comment leaks and prevention
<!-- -->
Proposing global instruction update
<!-- -->
A later clean-isolation run reproduced the same issue in exec --json and persisted session JSONL, so this is broader than a TUI-only rendering issue.
Environment
codex-cli: 0.143.0
release tag checked: rust-v0.143.0
model: gpt-5.5
model_reasoning_effort: xhigh
model_reasoning_summary: auto / concise / detailed / none tested
OS: macOS 26.5.1, arm64
terminal: Ghostty 1.3.2-HEAD-+05c3e29
install: Homebrew cask
subscription/auth: ChatGPT Pro auth
The regular user config originally had:
model_reasoning_summary = "auto"
model_reasoning_effort = "xhigh"
codex doctor --json reported overallStatus = warning, but the warning was about rollout/session inventory mismatch with an empty historical session file; it appears unrelated. Config parsing and auth were OK, and the responses websocket handshake succeeded.
Clean-isolation reproduction
I reproduced with a fresh temporary HOME and CODEX_HOME, copying only auth.json so the CLI could authenticate. The invocation used --ignore-user-config --ignore-rules, a temp working directory, and no user hooks, personal skills, AGENTS.md, project instructions, or user config.
Command shape:
ISO_ROOT=$(mktemp -d /tmp/codex-clean-iso2.XXXXXX)
ISO_HOME="$ISO_ROOT/home"
ISO_CODEX="$ISO_ROOT/codex"
mkdir -p "$ISO_HOME" "$ISO_CODEX"
cp "$HOME/.codex/auth.json" "$ISO_CODEX/auth.json"
HOME="$ISO_HOME" CODEX_HOME="$ISO_CODEX" \
codex --ask-for-approval never --sandbox workspace-write \
exec --json --ignore-user-config --ignore-rules --skip-git-repo-check \
-C "$ISO_ROOT" -m gpt-5.5 \
-c 'model_reasoning_effort="xhigh"' \
-c 'model_reasoning_summary="auto"' \
< "$ISO_ROOT/prompt.txt" > "$ISO_ROOT/out.jsonl"
Prompt:
Without using the network, inspect this empty temp workspace. Run `pwd` and `ls -la`, then answer with two concise bullet points: what files are present and the current working directory basename.
Result: reproduced in exec --json:
{"type":"item.completed","item":{"id":"item_0","type":"reasoning","text":"**Planning parallel command execution**\n\n<!-- -->"}}
The persisted isolated session JSONL also contained the placeholder in both agent_reasoning and completed reasoning.summary_text:
{"type":"event_msg","payload":{"type":"agent_reasoning","text":"**Planning parallel command execution**\n\n<!-- -->"}}
{"type":"response_item","payload":{"type":"reasoning","summary":[{"type":"summary_text","text":"**Planning parallel command execution**\n\n<!-- -->"}]}}
A minimal no-tool prompt in the same isolation style did not reproduce; its session had "summary": []. So the issue appears task-shape dependent and intermittent, but it reproduces without my local config/hooks/skills/project instructions.
Matrix
I then ran a small clean-isolation matrix: model_reasoning_summary x task shape.
summary_mode task_shape stdout <!-- --> session <!-- --> agent_reasoning events reasoning summary items
auto no_tool 0 0 0 0
auto tool 0 0 0 0
concise no_tool 0 0 1 1
concise tool 0 0 1 1
detailed no_tool 0 0 0 0
detailed tool 1 2 1 1
none no_tool 0 0 0 0
none tool 0 0 0 0
The detailed + tool run produced:
{"kind":"agent_reasoning","text":"**Running parallel directory commands**\n\n<!-- -->"}
{"kind":"reasoning","summary":["**Running parallel directory commands**\n\n<!-- -->"]}
I also repeated the auto + tool case five times with the same isolated prompt. Runs 1-4 did not emit summaries; run 5 emitted two reasoning summary items, both with the placeholder:
run stdout <!-- --> session <!-- --> agent_reasoning events reasoning summary items
1 0 0 0 0
2 0 0 0 0
3 0 0 0 0
4 0 0 0 0
5 2 4 2 2
Run 5 summary texts:
**Planning parallel execution of pwd and ls**\n\n<!-- -->
**Preparing to execute pwd and ls commands**\n\n<!-- -->
Local session frequency
In my normal ~/.codex/sessions/2026/07/08 data, before the clean-isolation checks, I found:
files with agent_reasoning containing <!-- -->: 9
agent_reasoning entries containing <!-- -->: 1680
Representative normal-session lines:
{"type":"event_msg","payload":{"type":"agent_reasoning","text":"**Clarifying Codex comment leaks and prevention**\n\n<!-- -->"}}
{"type":"event_msg","payload":{"type":"agent_reasoning","text":"**Proposing global instruction update**\n\n<!-- -->"}}
{"type":"response_item","payload":{"type":"reasoning","summary":[{"type":"summary_text","text":"**Clarifying Codex comment leaks and prevention**\n\n<!-- -->"},{"type":"summary_text","text":"**Proposing global instruction update**\n\n<!-- -->"}]}}
Those entries predated the user later copying the visible leak back into the conversation, so they were not merely echoes of user-provided text.
Source-path notes from rust-v0.143.0
I checked the release tag corresponding to the installed version. The user-facing path appears to pass summary text through without filtering:
codex-rs/codex-api/src/sse/responses.rs:354-369 maps response.reasoning_summary_text.delta/done into ResponseEvent::ReasoningSummaryDelta/Done using the server-provided delta/text as-is.
codex-rs/core/src/session/turn.rs:2337-2368 turns ReasoningSummaryDone.text into ReasoningContentDeltaEvent.delta as-is for streaming to the client.
codex-rs/core/src/event_mapping.rs:177-195 maps completed ResponseItem::Reasoning.summary[].text into ReasoningItem.summary_text with text.clone().
codex-rs/protocol/src/legacy_events.rs:104-110 turns every ReasoningItem.summary_text into AgentReasoningEvent { text: summary.clone() }.
codex-rs/exec/src/event_processor_with_jsonl_output.rs:150-158 joins ThreadItem::Reasoning.summary and emits it as an exec --json reasoning item if text.trim() is non-empty.
codex-rs/exec/src/event_processor_with_human_output.rs:468-482 similarly joins summary entries for human output.
codex-rs/tui/src/chatwidget/streaming.rs:200-235 appends reasoning deltas directly into reasoning_buffer and later calls new_reasoning_summary_block.
codex-rs/tui/src/history_cell/messages.rs:486-499 splits **header** from the remaining content. For **Header**\n\n<!-- -->, the remaining content is non-empty, so the final reasoning cell displays the literal placeholder.
Expected behavior
User-facing reasoning summaries should not expose literal empty HTML comment placeholders.
Either:
- Do not generate
<!-- --> in reasoning summary text, or
- Normalize/filter placeholder-only HTML comment lines before emitting user-facing reasoning summary events and rendering them.
If raw ResponseItem persistence should remain untouched for debugging, the filter could live in the user-facing conversion/rendering layers, such as the AgentReasoningEvent / ThreadItem::Reasoning / exec --json / TUI history conversion path.
Workaround
model_reasoning_summary = "none" parses successfully and avoided the issue in the matrix, but it disables reasoning summaries entirely.
What happened?
Reasoning summaries can contain a literal empty HTML comment placeholder,
<!-- -->. This placeholder then shows up in user-facing surfaces such as the TUI progress/history view andcodex exec --jsonoutput.Example observed in the TUI:
A later clean-isolation run reproduced the same issue in
exec --jsonand persisted session JSONL, so this is broader than a TUI-only rendering issue.Environment
The regular user config originally had:
codex doctor --jsonreportedoverallStatus = warning, but the warning was about rollout/session inventory mismatch with an empty historical session file; it appears unrelated. Config parsing and auth were OK, and the responses websocket handshake succeeded.Clean-isolation reproduction
I reproduced with a fresh temporary
HOMEandCODEX_HOME, copying onlyauth.jsonso the CLI could authenticate. The invocation used--ignore-user-config --ignore-rules, a temp working directory, and no user hooks, personal skills,AGENTS.md, project instructions, or user config.Command shape:
Prompt:
Result: reproduced in
exec --json:{"type":"item.completed","item":{"id":"item_0","type":"reasoning","text":"**Planning parallel command execution**\n\n<!-- -->"}}The persisted isolated session JSONL also contained the placeholder in both
agent_reasoningand completedreasoning.summary_text:{"type":"event_msg","payload":{"type":"agent_reasoning","text":"**Planning parallel command execution**\n\n<!-- -->"}} {"type":"response_item","payload":{"type":"reasoning","summary":[{"type":"summary_text","text":"**Planning parallel command execution**\n\n<!-- -->"}]}}A minimal no-tool prompt in the same isolation style did not reproduce; its session had
"summary": []. So the issue appears task-shape dependent and intermittent, but it reproduces without my local config/hooks/skills/project instructions.Matrix
I then ran a small clean-isolation matrix:
model_reasoning_summaryx task shape.The
detailed + toolrun produced:{"kind":"agent_reasoning","text":"**Running parallel directory commands**\n\n<!-- -->"} {"kind":"reasoning","summary":["**Running parallel directory commands**\n\n<!-- -->"]}I also repeated the
auto + toolcase five times with the same isolated prompt. Runs 1-4 did not emit summaries; run 5 emitted two reasoning summary items, both with the placeholder:Run 5 summary texts:
Local session frequency
In my normal
~/.codex/sessions/2026/07/08data, before the clean-isolation checks, I found:Representative normal-session lines:
{"type":"event_msg","payload":{"type":"agent_reasoning","text":"**Clarifying Codex comment leaks and prevention**\n\n<!-- -->"}} {"type":"event_msg","payload":{"type":"agent_reasoning","text":"**Proposing global instruction update**\n\n<!-- -->"}} {"type":"response_item","payload":{"type":"reasoning","summary":[{"type":"summary_text","text":"**Clarifying Codex comment leaks and prevention**\n\n<!-- -->"},{"type":"summary_text","text":"**Proposing global instruction update**\n\n<!-- -->"}]}}Those entries predated the user later copying the visible leak back into the conversation, so they were not merely echoes of user-provided text.
Source-path notes from
rust-v0.143.0I checked the release tag corresponding to the installed version. The user-facing path appears to pass summary text through without filtering:
codex-rs/codex-api/src/sse/responses.rs:354-369mapsresponse.reasoning_summary_text.delta/doneintoResponseEvent::ReasoningSummaryDelta/Doneusing the server-provideddelta/textas-is.codex-rs/core/src/session/turn.rs:2337-2368turnsReasoningSummaryDone.textintoReasoningContentDeltaEvent.deltaas-is for streaming to the client.codex-rs/core/src/event_mapping.rs:177-195maps completedResponseItem::Reasoning.summary[].textintoReasoningItem.summary_textwithtext.clone().codex-rs/protocol/src/legacy_events.rs:104-110turns everyReasoningItem.summary_textintoAgentReasoningEvent { text: summary.clone() }.codex-rs/exec/src/event_processor_with_jsonl_output.rs:150-158joinsThreadItem::Reasoning.summaryand emits it as anexec --jsonreasoning item iftext.trim()is non-empty.codex-rs/exec/src/event_processor_with_human_output.rs:468-482similarly joins summary entries for human output.codex-rs/tui/src/chatwidget/streaming.rs:200-235appends reasoning deltas directly intoreasoning_bufferand later callsnew_reasoning_summary_block.codex-rs/tui/src/history_cell/messages.rs:486-499splits**header**from the remaining content. For**Header**\n\n<!-- -->, the remaining content is non-empty, so the final reasoning cell displays the literal placeholder.Expected behavior
User-facing reasoning summaries should not expose literal empty HTML comment placeholders.
Either:
<!-- -->in reasoning summary text, orIf raw
ResponseItempersistence should remain untouched for debugging, the filter could live in the user-facing conversion/rendering layers, such as theAgentReasoningEvent/ThreadItem::Reasoning/exec --json/ TUI history conversion path.Workaround
model_reasoning_summary = "none"parses successfully and avoided the issue in the matrix, but it disables reasoning summaries entirely.