Esc interrupt leaves /goal status active instead of paused
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?
- Start an interactive Codex CLI session with the
goalsfeature enabled. - Create/start a
/goalso the thread has an active goal. - Let the active
/goalsession run for several hours. The observed case happened after roughly 4-5 hours. - Press
Escto interrupt the active turn. - 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
Escviachat.interrupt_turnincodex-rs/tui/src/keymap.rs. Ctrl+Cis handled inChatWidget::on_ctrl_cincodex-rs/tui/src/chatwidget/interaction.rs. Before it submitsAppCommand::interrupt_and_restore_prompt_if_no_output(), it callspause_active_goal_for_interrupt().pause_active_goal_for_interrupt()emitsAppEvent::SetThreadGoalStatus { status: Paused }when a thread has an active goal and an agent turn is running.- The normal
Escinterrupt path is handled lower down byBottomPane::handle_key_eventincodex-rs/tui/src/bottom_pane/mod.rs. When a task is running and the status row is visible, that path callsstatus.interrupt(). StatusIndicatorWidget::interrupt()only sends the interrupt operation. The bottom-pane/status path does not have goal/thread context, so it does not callpause_active_goal_for_interrupt()and does not emitAppEvent::SetThreadGoalStatus { status: Paused }.- Result:
Esccan abort the active turn while leaving the persisted thread goal status asactive. 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
BottomPanepredicate for "will this key event interrupt the running task?" using the same conditions already used byBottomPane::handle_key_event. - In
ChatWidget::handle_key_event, before delegating the key event toBottomPane, check that predicate. If it is true, callpause_active_goal_for_interrupt()first. - Then continue through the existing
BottomPane::handle_key_eventpath sostatus.interrupt()still sends the same interrupt operation as before. - Add regression coverage mirroring
ctrl_c_interrupt_pauses_active_goal_turn: with an activeThreadGoalStatus::Activeand a running task, pressingEscshould emitAppEvent::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.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗