CLI: resume paints a permanent Working footer when the thread is Idle
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What version of Codex CLI is running?
codex-cli 0.153.4
What subscription do you have?
Pro
Which model were you using?
GPT-5.6-Sol
What platform is your computer?
Darwin 25.6.0 arm64 arm
What terminal emulator and version are you using (if applicable)?
tmux
Codex doctor report
{
"note": "Full report omitted because it contains organization-
specific paths, MCP names, and endpoint-security metadata.",
"codexVersion": "0.153.4",
"platform": "macos-aarch64",
"installMethod": "Homebrew",
"authMode": "chatgpt",
"model": "gpt-5.6-sol",
"modelProvider": "openai",
"terminal": "iTerm2 3.6.11 with tmux 3.6a",
"latestVersion": "0.153.4"
}
What issue are you seeing?
When the TUI resumes an existing app-server thread, it can display
“Working (… • esc to interrupt)” indefinitely even though the
authoritative thread status is Idle and no turn is running.
Escape does not clear it because it sends a real turn/interrupt
request, but there is no active turn to interrupt. If a genuine turn
begins later, Escape interrupts that real turn while the stale Working
footer remains.
This is a TUI rendering-state bug rather than a hung model request.
What steps can reproduce the bug?
- Begin a Codex turn.
- End the app-server uncleanly while the turn is active, leaving a
persisted task_started without a subsequent task_complete,
turn_aborted, or other terminal event. - Restart the app-server.
- Attach the TUI using:
codex --remote ws://127.0.0.1: resume - Confirm that the resumed thread is Idle and has no active turn.
- Observe that the TUI nevertheless displays a permanent Working
footer. - Press Escape. The footer remains because no live turn exists to
complete or interrupt.
This lifecycle signature was verified in the affected rollout.
What is the expected behavior?
The TUI should use the resumed thread’s current ThreadStatus as the
authority for its activity indicator.
A historical InProgress turn should restore the Working footer only
when the live thread is Active. If the thread is Idle, the TUI should
render idle.
Additional information
During typed turn replay,
replay_thread_turns
unconditionally calls on_task_started() for every historical
TurnStatus::InProgress:
if matches!(status, TurnStatus::InProgress) {
// ...
self.on_task_started();
}
However,
AppServerStartedThread
does not carry the live ThreadStatus, and
started_thread_from_resume_response
copies response.thread.turns into the TUI bootstrap state while discarding
response.thread.status.
That lets a stale historical InProgress turn activate the local Working
widget even when the authoritative resumed thread is Idle. Since no live turn
exists, no later turn-completed event is guaranteed to clear the widget.
This looks like the same invariant addressed by #11852 / #12420 ("replayed
history must not force live active state") reappearing through the newer typed
turn replay path.
Suggested fix and regression cases
Carry the live ThreadStatus through the resumed-thread bootstrap and only
restore task-running UI from historical InProgress when the live thread is
actually Active. Also reconcile a live transition to Idle by clearing stale
task-running UI when no active turn remains.
Suggested tests:
ThreadStatus::Idle+ historicalTurnStatus::InProgress=> no Working
widget.ThreadStatus::Active+ historicalTurnStatus::InProgress=> Working
widget is restored.- A legitimately active resumed thread receives
ThreadStatusChanged(Idle)=> Working widget clears. - Resume an idle thread, then receive a real live
turn/started=> Working
widget appears normally.
#40563 is useful corroboration but a different layer: it documents the
producer-side persistence contradiction where history remains inProgress
while thread/resume reports an idle, input-capable runtime. This comment is
about the consumer-side TUI behavior when it receives that contradiction.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with codex-rs/tui/src/chatwidget/replay.rs and codex-rs/tui/src/app_server_session.rs, tracing how resumed thread status and historical turns enter the TUI state. Add regression coverage for Idle and Active threads with historical InProgress turns, plus an Active-to-Idle transition. Done means idle resumes do not show Working, active resumes do, and later real turns still display normally.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100