From 4457581dfffdb494e2709e6b2f2a77dc81e4cb3e Mon Sep 17 00:00:00 2001
From: Felipe Coury <felipe.coury@openai.com>
Date: Tue, 28 Apr 2026 11:00:07 -0300
Subject: [PATCH] fix(tui): let esc exit empty shell mode

---
 codex-rs/tui/src/bottom_pane/chat_composer.rs | 81 +++++++++++++++++++
 ...shell_command_escape_exits_empty_mode.snap | 14 ++++
 2 files changed, 95 insertions(+)
 create mode 100644 codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_mode_shell_command_escape_exits_empty_mode.snap

diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs
index 1a0e39d9c33c..2d99fea8a4f9 100644
--- a/codex-rs/tui/src/bottom_pane/chat_composer.rs
+++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs
@@ -2859,6 +2859,15 @@ impl ChatComposer {
         if self.handle_shortcut_overlay_key(&key_event) {
             return (InputResult::None, true);
         }
+        if self.is_bash_mode && key_event.code == KeyCode::Esc {
+            if let Some(pasted) = self.paste_burst.flush_before_modified_input() {
+                self.handle_paste(pasted);
+            }
+            if self.textarea.is_empty() {
+                self.is_bash_mode = false;
+                return (InputResult::None, true);
+            }
+        }
         if key_event.code == KeyCode::Esc {
             if self.is_empty() {
                 let next_mode = esc_hint_mode(self.footer_mode, self.is_task_running);
@@ -4635,6 +4644,19 @@ mod tests {
                 composer.set_text_content("!git status".to_string(), Vec::new(), Vec::new());
             },
         );
+
+        snapshot_composer_state(
+            "footer_mode_shell_command_escape_exits_empty_mode",
+            /*enhanced_keys_supported*/ true,
+            |composer| {
+                composer.set_status_line_enabled(/*enabled*/ true);
+                composer.set_status_line(Some(Line::from(
+                    "gpt-5.4 high fast · ~/code/codex-1 · Context 0% used",
+                )));
+                composer.set_text_content("!".to_string(), Vec::new(), Vec::new());
+                let _ = composer.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
+            },
+        );
     }
 
     #[test]
@@ -4697,6 +4719,65 @@ mod tests {
         );
     }
 
+    #[test]
+    fn esc_exits_empty_shell_mode() {
+        use crossterm::event::KeyCode;
+        use crossterm::event::KeyEvent;
+        use crossterm::event::KeyModifiers;
+
+        let (tx, _rx) = unbounded_channel::<AppEvent>();
+        let sender = AppEventSender::new(tx);
+        let mut composer = ChatComposer::new(
+            /*has_input_focus*/ true,
+            sender,
+            /*enhanced_keys_supported*/ false,
+            "Ask Codex to do anything".to_string(),
+            /*disable_paste_burst*/ false,
+        );
+
+        type_chars_humanlike(&mut composer, &['!']);
+        assert!(composer.is_bash_mode);
+        assert_eq!(composer.current_text(), "!");
+
+        let (result, needs_redraw) =
+            composer.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
+
+        assert!(matches!(result, InputResult::None));
+        assert!(needs_redraw);
+        assert!(!composer.is_bash_mode);
+        assert_eq!(composer.current_text(), "");
+    }
+
+    #[test]
+    fn esc_keeps_shell_mode_when_paste_burst_flushes_pending_text() {
+        use crossterm::event::KeyCode;
+        use crossterm::event::KeyEvent;
+        use crossterm::event::KeyModifiers;
+
+        let (tx, _rx) = unbounded_channel::<AppEvent>();
+        let sender = AppEventSender::new(tx);
+        let mut composer = ChatComposer::new(
+            /*has_input_focus*/ true,
+            sender,
+            /*enhanced_keys_supported*/ false,
+            "Ask Codex to do anything".to_string(),
+            /*disable_paste_burst*/ false,
+        );
+
+        type_chars_humanlike(&mut composer, &['!']);
+        let _ = composer.handle_key_event(KeyEvent::new(KeyCode::Char('g'), KeyModifiers::NONE));
+        assert!(composer.is_in_paste_burst());
+        assert_eq!(composer.current_text(), "!");
+
+        let (result, needs_redraw) =
+            composer.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
+
+        assert!(matches!(result, InputResult::None));
+        assert!(needs_redraw);
+        assert!(composer.is_bash_mode);
+        assert_eq!(composer.current_text(), "!g");
+    }
+
     #[test]
     fn footer_collapse_snapshots() {
         fn setup_collab_footer(
diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_mode_shell_command_escape_exits_empty_mode.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_mode_shell_command_escape_exits_empty_mode.snap
new file mode 100644
index 000000000000..dac77a38da37
--- /dev/null
+++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__chat_composer__tests__footer_mode_shell_command_escape_exits_empty_mode.snap
@@ -0,0 +1,14 @@
+---
+source: tui/src/bottom_pane/chat_composer.rs
+assertion_line: 4485
+expression: terminal.backend()
+---
+"                                                                                                    "
+"› Ask Codex to do anything                                                                          "
+"                                                                                                    "
+"                                                                                                    "
+"                                                                                                    "
+"                                                                                                    "
+"                                                                                                    "
+"                                                                                                    "
+"  gpt-5.4 high fast · ~/code/codex-1 · Context 0% used                                              "
