TUI loses or misreports the active turn timer after state replay/restore
What version of Codex CLI is running?
0.125.0
What subscription do you have?
Plus
Which model were you using?
_No response_
What platform is your computer?
Linux
What terminal emulator and version are you using (if applicable)?
_No response_
What issue are you seeing?
In the TUI, the elapsed "working" timer for an active assistant turn can be reset, lost, or reported incorrectly when the UI restores input/thread state or replays turn events.
The visible symptom is that the status timer and the final "Worked for ..." separator are not consistently based on the original turn start time. In some flows, the UI falls back to the current status widget elapsed value, so restoring/replaying state can make the elapsed time appear shorter than the actual active turn duration, or omit useful elapsed-time information from the separator.
The underlying issue appears to be that the TUI treats the bottom-pane status widget's local timer state as the source of truth. That widget can be reconstructed or refreshed as part of normal UI state handling, so its local elapsed value is not stable enough to represent the lifetime of the active turn.
The active turn itself already has a more durable lifecycle: turn-start notifications can carry a start timestamp, and turn-complete notifications can carry the completed duration. The TUI should use those turn lifecycle values as the source of truth, then render them through the status widget, instead of deriving turn duration from whichever status widget instance is currently mounted.
What steps can reproduce the bug?
One way to reproduce is to start a Codex CLI turn that remains active long enough for the status timer to advance, then trigger a path that restores or replays TUI state while that turn is still in progress, such as resuming/replaying thread state or switching through UI state that reconstructs the bottom pane.
After the restore/replay, observe the active status timer and the final separator inserted when the turn completes. The elapsed time can be based on the reconstructed widget state instead of the original turn start timestamp.
I validated the fix direction locally. The implementation area is the TUI turn lifecycle and status rendering path, specifically:
- The chat widget should store the active turn's start time as turn state, independent from the bottom-pane status widget.
- When a turn-start event is received, the TUI should derive the local monotonic start instant from the event's start timestamp when available, and fall back to the current instant only when no timestamp is provided.
- When thread/input state is captured and restored, the active turn start time should be included with the restored TUI state, so reconstructing the bottom pane does not reset the timer.
- When the bottom-pane status indicator is recreated or refreshed while a turn is still running, it should receive the preserved start time and continue rendering elapsed time from that original start point.
- When a turn completes, the final "Worked for ..." separator should prefer the completed turn duration from the turn-complete notification. If that duration is unavailable, it can fall back to elapsed time calculated from the preserved active turn start time.
- Any separator baseline used to avoid double-counting work across streamed/final message boundaries should be preserved with the same input state, so elapsed-time deltas remain correct after restore.
This works because it separates the durable turn lifecycle state from the transient UI widget state. The status widget becomes a renderer of the active turn timer rather than the owner of the timer. Replaying or restoring UI state can replace the widget, but it no longer changes the underlying turn start time. Completing a turn also becomes deterministic because the separator can use the server/core-reported completed duration when it exists.
With that approach, the timer survives TUI state restore/replay and the final elapsed-time separator reflects the actual turn duration instead of the reconstructed widget's local elapsed value.
What is the expected behavior?
The TUI should preserve the active turn's original start time across restore/replay paths. The status timer should continue from the original start time, and the final "Worked for ..." separator should use the actual completed turn duration when that duration is available.
Additional information
A related display detail is that short completed turns should still be allowed to show elapsed-time information in the final separator. For example, a completed turn with a duration such as 12 seconds can show Worked for 12s instead of suppressing the elapsed-time label. That makes the behavior easier to verify and keeps short turn summaries consistent with longer ones.
No generated source code is included in this report. The intent is to describe the bug, the affected TUI state ownership, and the validated fix direction.
This report was completed with assistance from Codex CLI.
7 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
• I noticed that this partially overlaps with #19929.
That PR appears to address one important part of the issue: the final
Worked for ...separator now uses the cumulative completed turn duration, so the final elapsed-time summary is no longer derived from incremental status-widget timing. That should fix cases where thefinal separator reports an incorrect total after streamed/final message boundaries.
The remaining concern is slightly different: the active in-progress TUI status timer can still be tied to transient bottom-pane/status-widget state. If that widget is reconstructed during restore/replay or other TUI state transitions while a turn is still running, the
visible running timer may reset or become detached from the original turn start time.
So I think #19929 fixes the completed-turn summary path, but may not fully cover preserving the active turn start time across TUI restore/replay paths. The missing piece would be to keep the active turn start timestamp as durable chat/turn state, restore it along with
input/thread state, and reapply it to the bottom-pane status indicator when the UI is reconstructed.
• One concrete case where I still see this is when using
/agent.If I switch from the main session to another sub-agent with
/agent, then switch back, the active turn timer can reset. I also see another reset when the main session has instructed sub-agents to close themselves when they are no longer needed, and one of those sub-agents gets closed while the main turn is still active.
I locally fixed this behavior in the Codex CLI source and confirmed that, with the fix, switching between agents and closing a sub-agent no longer resets the active turn timer. This seems to support that the remaining issue is specifically about preserving the active turn
timer across TUI agent/session state transitions, not just about the final completed-turn summary.
This reply was completed with assistance from Codex CLI.
I can confirm a concrete regression on
v0.128.0(rust-v0.128.0).Observed behavior:
That makes this look like the active turn timer can lose previously accumulated time or be re-derived from transient UI state, rather than just a formatting issue. The elapsed display itself appears to support values above 12 hours, so this seems closer to a state restore/replay timer regression.
If useful, I can provide the exact reproduction details from the session I observed.
@Eric-Terminal, are you saying that you think that you are seeing a regression in 0.128.0 versus 0.127.0? If so, please file a new bug report. This bug was filed against 0.125.0, so it cannot be the same as a regression in 0.128.0.
Ah, got it. I saw this issue was still open and my situation seemed very similar, so I thought I'd mention it here first.
107-tui-preserve-working-timer.patch
108-tui-preserve-status-timer-after-recreate.patch
these 2 codex-generated patches address the above-mentioned issue on version 0.129.0-alpha.3 at least for me
without these 2 patches, the timer will always reset in most cases
Related request: #27203
This issue is about preserving the active turn timer across TUI restore/replay. #27203 is a complementary display-layer request: optionally prefix every visible output block with a host-generated timestamp, so users can still reconstruct real wall-clock progress across replay/restore/compaction even if assistant text contains stale or incorrect timestamps.