Resume replay can surface a stale assistant final message after restart from archived/removed subagent history

Open 💬 1 comment Opened Apr 11, 2026 by nos1609

What version of Codex CLI is running?

codex-cli 0.120.0

What subscription do you have?

Pro

Which model were you using?

gpt-5.4

What platform is your computer?

Linux 6.12.0-124.47.1.el10_1.x86_64 x86_64 unknown

What terminal emulator and version are you using (if applicable)?

VSCode

What issue are you seeing?

After reopening Codex, an old assistant message kept reappearing as the visible final message for the current thread, even after I removed the obvious stale text from local session storage.

I verified that:

  • the exact phrase was removed from ~/.codex
  • the child subagent thread was archived in ~/.codex/state_5.sqlite
  • the parent->child spawn edge was removed
  • the child rollout JSONL was moved out of ~/.codex/sessions/
  • the parent rollout JSONL was edited to remove the exact stale phrase

Despite that, after restarting the window, Codex still rendered that old assistant final message.

This looks like replay/resume is reconstructing the stale final message from persisted history sources rather than only from the latest valid turn state.

What steps can reproduce the bug?

Thread id involved:

  • parent thread: 019d6838-2c9c-76c3-a02d-96a0cde9a4b7
  • child subagent thread: 019d7def-2e30-7970-bf17-012e7ec95c90

What I observed locally suggests this repro shape:

  1. Start a main thread.
  2. Spawn one or more subagents from it.
  3. Let one subagent produce a visible assistant final message.
  4. Continue working in the parent thread so newer messages exist.
  5. Restart/reopen Codex and resume the parent thread.
  6. Observe that an older assistant final message can reappear as the visible final message for the parent thread.

I also tested local cleanup after the fact:

  1. Archive the child thread in ~/.codex/state_5.sqlite.
  2. Remove the corresponding row from thread_spawn_edges.
  3. Move the child rollout JSONL out of ~/.codex/sessions/.
  4. Remove the exact stale phrase from the parent rollout JSONL.
  5. Restart the client.

Actual result:

  • the stale assistant final message can still reappear.

Relevant local files during investigation:

  • parent rollout:

~/.codex/sessions/2026/04/07/rollout-2026-04-07T16-53-25-019d6838-2c9c-76c3-a02d-96a0cde9a4b7.jsonl

  • child rollout:

~/.codex/sessions/2026/04/11/rollout-2026-04-11T22-05-20-019d7def-2e30-7970-bf17-012e7ec95c90.jsonl

What is the expected behavior?

On resume/replay, Codex should only show the latest valid assistant state for the resumed thread.

Archived or removed child-thread history should not be able to resurrect an older assistant final message in the parent thread UI.

Replay should not treat stale historical AgentMessage / TurnComplete.last_agent_message / compacted history as a fresh final assistant response for the current thread.

Additional information

I inspected the latest main branch locally and found a likely replay path issue.

Suspect areas:

  • codex-rs/tui/src/chatwidget.rs
  • replayed EventMsg::AgentMessage(...) still records markdown into last_agent_markdown
  • on_task_complete(last_agent_message, from_replay) still emits Notification::AgentTurnComplete even when from_replay == true
  • codex-rs/rollout/src/recorder.rs
  • load_rollout_items() and get_rollout_history() replay EventMsg and Compacted items as-is

That combination seems capable of reviving stale final-message sources on resume.

A plausible fix direction:

  1. Do not emit AgentTurnComplete notifications during replay.
  2. Restrict replay so only the latest valid turn contributes final-message state.
  3. Add a regression test covering:
  • parent thread
  • spawned child thread
  • archived/removed child
  • replay with legacy AgentMessage
  • TurnComplete.last_agent_message
  • compacted history

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗