You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
TUI: footer duplicates and “Working” status briefly disappears after interrupt + immediate resubmission #9811
What terminal emulator and version are you using (if applicable)?
wezterm
What issue are you seeing?
If I interrupt a running turn with Esc and immediately submit a new message, the bottom area of the TUI can briefly look idle. During that window, I sometimes see the footer rendered twice (two different context left percentages on separate lines), and the “Working” status line is missing until the backend catches up.
What steps can reproduce the bug?
Start the Codex TUI (e.g. run codex).
Submit any prompt so the agent starts working.
Press Esc to interrupt the current turn.
Immediately submit another message (press Enter to send).
Watch the bottom area right after submission.
What is the expected behavior?
Right after the new submission, the UI should show a clear “Working” (or equivalent) status and should not render duplicate footer lines.
Fresh diagnosis against main at 536f86e: this is a cross-platform, terminal-independent pending-turn lifecycle bug. The original Linux/WezTerm environment is consistent with the shared code path involved.
What happens
The user interrupts an active turn and immediately submits another message. The TUI records that pending input should be sent after interruption through submit_pending_steers_after_interrupt.
When the interrupted turn completes, on_interrupted_turn calls finalize_turn. This clears the active lifecycle and makes the bottom pane idle.
The same interruption handler immediately resubmits the pending steer as a new turn.
The submission path sets user_turn_pending_start = true, correctly recording that work has been submitted but has not emitted TurnStarted.
update_task_running_state, however, derives the bottom-pane state only from an active turn, review mode, and MCP startup. It does not include the pending-start state.
The status header may already say Working, but the status row is not rendered because the bottom pane considers itself idle.
This also accounts for the footer artifact. The bottom pane temporarily loses the status row and contracts, moving the footer upward. When TurnStarted arrives, the row returns and the pane expands again. That height churn allows the previous and current footer rows to remain visible at different positions during the transition.
Why the state is fragile
Pending-start handling is split across several paths:
AppEventSender::send logs and swallows enqueue failures, so callers cannot distinguish accepted dispatch from rejection.
handle_turn_start_rejection finalizes shared turn state rather than releasing state owned specifically by the rejected attempt.
Required invariant
Once a start-producing operation is accepted by its local dispatch boundary, the TUI should remain task-running until either:
the corresponding TurnStarted consumes the pending reservation, or
that same dispatch attempt fails and rolls back its reservation.
A rejected attempt must not clear an older accepted pending start.
The task-running calculation should include pending-start state alongside active turns, review mode, and MCP startup. The same reservation lifecycle should cover UserTurn, Compact, Review, and RunUserShellCommand.
Transport side effects should occur only after local acceptance:
Activity
etraut-openai commentedon Feb 23, 2026
A proposed fix was submitted in this PR.
astral303 commentedon Aug 21, 2026
Fresh diagnosis against
mainat 536f86e: this is a cross-platform, terminal-independent pending-turn lifecycle bug. The original Linux/WezTerm environment is consistent with the shared code path involved.What happens
submit_pending_steers_after_interrupt.on_interrupted_turncallsfinalize_turn. This clears the active lifecycle and makes the bottom pane idle.user_turn_pending_start = true, correctly recording that work has been submitted but has not emittedTurnStarted.update_task_running_state, however, derives the bottom-pane state only from an active turn, review mode, and MCP startup. It does not include the pending-start state.user_turn_pending_startset whilebottom_pane.is_task_running()remains false.TurnStartedinvokeson_task_started.The status header may already say
Working, but the status row is not rendered because the bottom pane considers itself idle.This also accounts for the footer artifact. The bottom pane temporarily loses the status row and contracts, moving the footer upward. When
TurnStartedarrives, the row returns and the pane expands again. That height churn allows the previous and current footer rows to remain visible at different positions during the transition.Why the state is fragile
Pending-start handling is split across several paths:
prepare_local_op_submission.submit_op.prepare_local_op_submissionruns before a direct channel send is known to have succeeded.AppEventSender::sendlogs and swallows enqueue failures, so callers cannot distinguish accepted dispatch from rejection.handle_turn_start_rejectionfinalizes shared turn state rather than releasing state owned specifically by the rejected attempt.Required invariant
Once a start-producing operation is accepted by its local dispatch boundary, the TUI should remain task-running until either:
TurnStartedconsumes the pending reservation, orA rejected attempt must not clear an older accepted pending start.
The task-running calculation should include pending-start state alongside active turns, review mode, and MCP startup. The same reservation lifecycle should cover
UserTurn,Compact,Review, andRunUserShellCommand.Transport side effects should occur only after local acceptance:
submit_opsuccessfully sends the command.AppEvent::CodexOppath.Suggested regression coverage
A deterministic test does not need timing or a real terminal:
TurnStarted.Workingrow and one footer.TurnStartedand verify that the bottom-pane height does not change.Keep pending turn status visible before start
Keep pending turn status visible before start
Keep pending turn status visible before start
Keep pending turn status visible before start
Keep pending turn status visible before start
Keep pending turn status visible before start
Keep pending turn status visible before start
Keep pending turn status visible before start
Keep pending turn status visible before start
Keep pending turn status visible before start
Keep pending turn status visible before start
Keep pending turn status visible before start
Keep pending turn status visible before start