Files
codex/docs/exit-confirmation-prompt-design.md
Josh McKinney a489b64cb5 feat(tui): retire the tui2 experiment (#9640)
## Summary
- Retire the experimental TUI2 implementation and its feature flag.
- Remove TUI2-only config/schema/docs so the CLI stays on the
terminal-native path.
- Keep docs aligned with the legacy TUI while we focus on redraw-based
improvements.

## Customer impact
- Retires the TUI2 experiment and keeps Codex on the proven
terminal-native UI while we invest in redraw-based improvements to the
existing experience.

## Migration / compatibility
- If you previously set tui2-related options in config.toml, they are
now ignored and Codex continues using the existing terminal-native TUI
(no action required).

## Context
- What worked: a transcript-owned viewport delivered excellent resize
rewrap and high-fidelity copy (especially for code).
- Why stop: making that experience feel fully native across the
environment matrix (terminal emulator, OS, input modality, multiplexer,
font/theme, alt-screen behavior) creates a combinatorial explosion of
edge cases.
- What next: we are focusing on redraw-based improvements to the
existing terminal-native TUI so scrolling, selection, and copy remain
native while resize/redraw correctness improves.

## Testing
- just write-config-schema
- just fmt
- cargo clippy --fix --all-features --tests --allow-dirty --allow-no-vcs
-p codex-core
- cargo clippy --fix --all-features --tests --allow-dirty --allow-no-vcs
-p codex-cli
- cargo check
- cargo test -p codex-core
- cargo test -p codex-cli
2026-01-22 01:02:29 +00:00

3.9 KiB

Exit and shutdown flow (tui)

This document describes how exit, shutdown, and interruption work in the Rust TUI (codex-rs/tui). It is intended for Codex developers and Codex itself when reasoning about future exit/shutdown changes.

This doc replaces earlier separate history and design notes. High-level history is summarized below; full details are captured in PR #8936.

Terms

  • Exit: end the UI event loop and terminate the process.
  • Shutdown: request a graceful agent/core shutdown (Op::Shutdown) and wait for ShutdownComplete so cleanup can run.
  • Interrupt: cancel a running operation (Op::Interrupt).

Event model (AppEvent)

Exit is coordinated via a single event with explicit modes:

  • AppEvent::Exit(ExitMode::ShutdownFirst)
    • Prefer this for user-initiated quits so cleanup runs.
  • AppEvent::Exit(ExitMode::Immediate)
    • Escape hatch for immediate exit. This bypasses shutdown and can drop in-flight work (e.g., tasks, rollout flush, child process cleanup).

App is the coordinator: it submits Op::Shutdown and it exits the UI loop only when ExitMode::Immediate arrives (typically after ShutdownComplete).

User-triggered quit flows

Ctrl+C

Priority order in the UI layer:

  1. Active modal/view gets the first chance to consume (BottomPane::on_ctrl_c).
    • If the modal handles it, the quit flow stops.
    • When a modal/popup handles Ctrl+C, the quit shortcut is cleared so dismissing a modal cannot accidentally prime a subsequent Ctrl+C to quit.
  2. If the user has already armed Ctrl+C and the 1 second window has not expired, the second Ctrl+C triggers shutdown-first quit immediately.
  3. Otherwise, ChatWidget arms Ctrl+C and shows the quit hint (ctrl + c again to quit) for 1 second.
  4. If cancellable work is active (streaming/tools/review), ChatWidget submits Op::Interrupt.

Ctrl+D

  • Only participates in quit when the composer is empty and no modal is active.
    • On first press, show the quit hint (same as Ctrl+C) and start the 1 second timer.
    • If pressed again while the hint is visible, request shutdown-first quit.
  • With any modal/popup open, key events are routed to the view and Ctrl+D does not attempt to quit.

Slash commands

  • /quit, /exit, /logout request shutdown-first quit without a prompt, because slash commands are harder to trigger accidentally and imply clear intent to quit.

/new

  • Uses shutdown without exit (suppresses ShutdownComplete) so the app can start a fresh session without terminating.

Shutdown completion and suppression

ShutdownComplete is the signal that core cleanup has finished. The UI treats it as the boundary for exit:

  • ChatWidget requests Exit(Immediate) on ShutdownComplete.
  • App can suppress a single ShutdownComplete when shutdown is used as a cleanup step (e.g., /new).

Edge cases and invariants

  • Review mode counts as cancellable work. Ctrl+C should interrupt review, not quit.
  • Modal open means Ctrl+C/Ctrl+D should not quit unless the modal explicitly declines to handle Ctrl+C.
  • Immediate exit is not a normal user path; it is a fallback for shutdown completion or an emergency exit. Use it sparingly because it skips cleanup.

Testing expectations

At a minimum, we want coverage for:

  • Ctrl+C while working interrupts, does not quit.
  • Ctrl+C while idle and empty shows quit hint, then shutdown-first quit on second press.
  • Ctrl+D with modal open does not quit.
  • /quit / /exit / /logout quit without prompt, but still shutdown-first.
    • Ctrl+D while idle and empty shows quit hint, then shutdown-first quit on second press.

History (high level)

Codex has historically mixed "exit immediately" and "shutdown-first" across quit gestures, largely due to incremental changes and regressions in state tracking. This doc reflects the current unified, shutdown-first approach. See PR #8936 for the detailed history and rationale.