Skip to content

TUI: footer duplicates and “Working” status briefly disappears after interrupt + immediate resubmission #9811

Open

Description

@dyxushuai

What version of Codex is running?

codex-cli 0.89.0

What subscription do you have?

plus

Which model were you using?

gpt-5.2

What platform is your computer?

Linux 6.17.4-2-pve x86_64 unknown

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?

  1. Start the Codex TUI (e.g. run codex).
  2. Submit any prompt so the agent starts working.
  3. Press Esc to interrupt the current turn.
  4. Immediately submit another message (press Enter to send).
  5. 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.

Additional information

Screenshot

Image

Activity

  1. added
    CLIIssues related to the Codex CLI
    TUIIssues related to the terminal user interface: text input, menus and dialogs, and terminal display
    on Jan 24, 2026
  2. etraut-openai commented on Feb 23, 2026

    @etraut-openai
    Contributor

    A proposed fix was submitted in this PR.

  3. astral303 commented on Aug 21, 2026

    @astral303

    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

    1. 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.
    2. When the interrupted turn completes, on_interrupted_turn calls finalize_turn. This clears the active lifecycle and makes the bottom pane idle.
    3. The same interruption handler immediately resubmits the pending steer as a new turn.
    4. The submission path sets user_turn_pending_start = true, correctly recording that work has been submitted but has not emitted TurnStarted.
    5. 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.
    6. The TUI therefore has user_turn_pending_start set while bottom_pane.is_task_running() remains false.
    7. The gap ends only when TurnStarted invokes on_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 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:

    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:

    Suggested regression coverage

    A deterministic test does not need timing or a real terminal:

    • Submit a turn successfully but do not emit TurnStarted.
    • Assert that pending-start and bottom-pane task-running states are both true.
    • Render a snapshot containing one Working row and one footer.
    • Emit TurnStarted and verify that the bottom-pane height does not change.
    • Cover failed direct enqueue, app-server rejection, and a failed second attempt while an older reservation remains active.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    TUIIssues related to the terminal user interface: text input, menus and dialogs, and terminal displaybugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions