TUI /agent replay duplicates subagent reasoning summaries and concatenates Markdown as ****
What version of Codex CLI is running?
codex-cli 0.144.0, from a custom fork based on current openai/codex source.
The relevant thread buffering, snapshot replay, and reasoning rendering paths described below are present on upstream main at 9e552e9d15ba52bed7077d5357f3e18e330f8f38. The fork's changes in these files are unrelated to reasoning buffering/replay.
What subscription do you have?
Pro 20x.
Which model were you using?
GPT-5.6 Sol.
The affected model configuration uses Responses Lite, but the persisted completed reasoning items are structurally correct. The duplication and **** concatenation appear only in the subagent TUI replay path.
What platform is your computer?
Linux 6.17.0-35-generic x86_64.
What terminal emulator and version are you using (if applicable)?
Ghostty 1.3.1 (TERM=xterm-ghostty), no tmux/zellij.
Codex doctor report
Not applicable. The parent and child rollout files preserve the reasoning summaries before TUI rendering and show that the completed Responses items contain separate, correctly ordered summary[] entries.
Affected parent thread:
019f4598-c4f7-7353-8030-9a82e6089756
Affected child threads:
019f538c-11f8-7fa2-af7f-dd14f8a1e6da
019f53c3-1fb3-7dc0-90b6-47a3b0a32083
019f54e9-9500-7723-87e0-c4056ce27305
What issue are you seeing?
Reasoning summaries shown while attaching to or switching into a subagent thread can be duplicated. The boundary between a stale live reasoning part and the replayed completed item can also be concatenated as four literal asterisks:
• Adjusting unsupported timestamp thresholds****Verifying timestamp handling for candidates
Adjusting unsupported timestamp thresholds
Another affected child rendered:
Confirming test execution behavior and doc mismatches****Identifying bug in recovery query
Detecting query test coverage gaps
Confirming test execution behavior and doc mismatches
• Classifying findings by priority and inspecting generated docs****Verifying test naming and documentation accuracy
Classifying findings by priority and inspecting generated docs
This occurs only in subagent thinking/reasoning streams. I have not observed it in the primary agent's normal TUI session.
The rollout evidence separates backend output from client rendering. For example, the completed response item for the first sample contains two distinct summary entries:
[
{"type":"summary_text","text":"**Verifying timestamp handling for candidates**"},
{"type":"summary_text","text":"**Adjusting unsupported timestamp thresholds**"}
]
The corresponding persisted legacy agent_reasoning event contains only the last part:
{"type":"agent_reasoning","text":"**Adjusting unsupported timestamp thresholds**"}
The same pattern exists in the other affected child: a clean completed response_item.reasoning.summary[], alongside a live/final reasoning event containing the last part. The malformed **** string does not exist in the rollout.
This points to a client-side live-plus-replay state defect:
subagent live reasoning event/deltas
→ last part remains in reasoning state or the buffered event stream
→ completed reasoning item is also restored from the thread snapshot
→ replay appends the first completed part to the stale bold-markdown part
→ `**last****first**` renders as literal `****`
→ the completed summary is rendered again, duplicating the block
What steps can reproduce the bug?
- Start a primary TUI thread.
- Spawn a subagent that emits a multi-part reasoning summary.
- Keep the primary agent selected while the subagent runs, so its live notifications are buffered.
- Let the child complete a reasoning item.
- Open
/agentand attach to the child thread, or switch away and back after its snapshot has acquired the completed turn. - Observe that:
- the last live summary part appears once before the completed replay;
- the first replayed part is sometimes joined directly to it as
****; - one or more summary parts appear a second time in the completed block.
For deterministic coverage, construct a child-thread snapshot containing both:
- buffered
reasoningSummaryTextDelta/ reasoning completion notifications; and - a completed
ThreadItem::Reasoningwith the same summary parts insnapshot.turns.
Then attach the TUI to that child and inspect the rendered transcript.
What is the expected behavior?
Each completed reasoning summary part should appear exactly once, in order, regardless of whether the child was viewed live or attached after completion.
Switching into a child thread should reconcile buffered live events with the authoritative completed snapshot. It should not replay both representations of the same reasoning item, and Markdown boundaries from two representations should never be concatenated.
Source analysis
Every incoming notification is appended to the per-thread replay buffer, including notifications that are also forwarded live:
ThreadEventStore::push_notification() retains those notifications, and snapshot() includes both the stored turns and essentially all buffered notifications:
Selecting a subagent reconstructs the ChatWidget and replays that combined snapshot:
The snapshot replay renders completed turns first, then replays every retained buffered event:
A replayed completed ThreadItem::Reasoning feeds every full summary part through the same delta accumulator before finalizing it:
That accumulator performs a raw push_str() with no identity check or separator. A section boundary only moves the existing string into reasoning_summary_parts; it does not reconcile live and completed representations:
The combination makes duplicate live-plus-snapshot replay possible and explains the literal ****: two independently valid bold strings, **last** and **first**, are appended without a boundary.
Suggested fix
Reconcile buffered notifications against the refreshed/completed turn snapshot before rendering:
- Treat completed
snapshot.turnsas authoritative for items they contain. - Drop buffered reasoning deltas, section breaks, and completion notifications already covered by the same turn/item ID.
- Retain only events newer than the snapshot or events whose state is not represented in it.
- Keep explicit per-item reasoning assembly state rather than one unkeyed widget-level string, or at minimum reset it when changing from live event replay to completed-item replay.
- Add a regression snapshot test that attaches to an inactive child with both live reasoning events and a completed reasoning item, and asserts:
- each part appears once;
- order is preserved;
- no
****boundary appears; - switching away and back remains idempotent.
Related issues
- #31664 covers literal
<!-- -->placeholders emitted inside reasoning summary events. One affected child here also has that placeholder in its persisted completed response, so that symptom is backend/model-side and separate from the duplication. - #16801 covers missing reasoning summaries and response-event ordering/crashes, but not subagent thread attachment replaying both retained live state and a completed snapshot.
I found no open or closed issue specifically covering the subagent live-plus-replay duplication and **** concatenation path.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗