Esc interrupt leaves /goal status active instead of paused

Resolved 💬 1 comment Opened Jun 13, 2026 by xxxxxmax Closed Jun 18, 2026

What version of Codex CLI is running?

codex-cli 0.139.0

What subscription do you have?

ChatGPT Pro ($200/month)

Which model were you using?

gpt-5.5

What platform is your computer?

Ubuntu 24.04 on WSL2, Linux 6.6.87.2-microsoft-standard-WSL2 x86_64

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

Warp Terminal v0.2026.06.03.09.49.stable_02 (TERM_PROGRAM=WarpTerminal)

What issue are you seeing?

After an interactive Codex CLI /goal session has been running for several hours (observed after roughly 4-5 hours), pressing Esc to interrupt the active turn can leave the goal active instead of pausing it. After that, sending another message may appear to do nothing or leave the session in a confusing state, because the goal lifecycle still believes the goal is active even though the user intentionally interrupted the current turn.

This seems specific to the Esc interrupt path. The Ctrl+C interrupt path already pauses an active goal before sending the interrupt operation.

What steps can reproduce the bug?

  1. Start an interactive Codex CLI session with the goals feature enabled.
  2. Create/start a /goal so the thread has an active goal.
  3. Let the active /goal session run for several hours. The observed case happened after roughly 4-5 hours.
  4. Press Esc to interrupt the active turn.
  5. Send a follow-up message in the same session.

Observed behavior: the session can look unresponsive or fail to proceed normally after the interrupt, and the goal remains active rather than being transitioned to paused.

What is the expected behavior?

Interrupting an active /goal turn with Esc should pause the active goal, matching the existing Ctrl+C interrupt behavior. After the interrupt, the user should be able to send a follow-up message or explicitly resume the goal from a consistent paused state.

Additional information

A local session/code-path check points to a TUI interrupt-state mismatch, not a model or rate-limit issue.

Bug

  • The default chat interrupt binding is Esc via chat.interrupt_turn in codex-rs/tui/src/keymap.rs.
  • Ctrl+C is handled in ChatWidget::on_ctrl_c in codex-rs/tui/src/chatwidget/interaction.rs. Before it submits AppCommand::interrupt_and_restore_prompt_if_no_output(), it calls pause_active_goal_for_interrupt().
  • pause_active_goal_for_interrupt() emits AppEvent::SetThreadGoalStatus { status: Paused } when a thread has an active goal and an agent turn is running.
  • The normal Esc interrupt path is handled lower down by BottomPane::handle_key_event in codex-rs/tui/src/bottom_pane/mod.rs. When a task is running and the status row is visible, that path calls status.interrupt().
  • StatusIndicatorWidget::interrupt() only sends the interrupt operation. The bottom-pane/status path does not have goal/thread context, so it does not call pause_active_goal_for_interrupt() and does not emit AppEvent::SetThreadGoalStatus { status: Paused }.
  • Result: Esc can abort the active turn while leaving the persisted thread goal status as active. A follow-up user message can then enter a confusing state because the turn was interrupted, but the goal lifecycle still believes the goal is active rather than paused.

Proposed fix

  • Keep the actual interrupt dispatch in the existing bottom-pane/status path so current composer, popup, Vim, /agent, and status-row behavior stays unchanged.
  • Expose or centralize the BottomPane predicate for "will this key event interrupt the running task?" using the same conditions already used by BottomPane::handle_key_event.
  • In ChatWidget::handle_key_event, before delegating the key event to BottomPane, check that predicate. If it is true, call pause_active_goal_for_interrupt() first.
  • Then continue through the existing BottomPane::handle_key_event path so status.interrupt() still sends the same interrupt operation as before.
  • Add regression coverage mirroring ctrl_c_interrupt_pauses_active_goal_turn: with an active ThreadGoalStatus::Active and a running task, pressing Esc should emit AppEvent::SetThreadGoalStatus { status: Paused } before the interrupt operation.

The contributing guide says external code PRs are by invitation only, so this is filed as a bug report with analysis rather than an unsolicited PR. A small focused patch can be provided if invited.

View original on GitHub ↗

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