fix: Prevent /review crash when entering Esc on steer message (#22879)

This changes the `/review` escape path so `Esc` no longer behaves like
the normal queued-follow-up interrupt flow while a review is running.
Steering is not currently supported in `/review` mode, without this
change users are able to attempt a steer but it leads to a crash (see
#22815). If the user has already tried to send additional guidance
during `/review`, the TUI now keeps the review running and shows a
warning that steer messages are not supported in that mode, while still
pointing users to `Ctrl+C` if they actually want to cancel. It also adds
regression coverage for the review-specific warning behavior. When users
do cancel with Ctrl+C during /review, the TUI now tolerates the
active-turn race that can happen during review handoff, and any queued
steer messages are restored to the composer instead of being discarded.

- Special-case `Esc` during an active `/review` when follow-up steer
input is pending or has already been deferred.
- Show a clear warning instead of interrupting the running review.
- Make the Ctrl+C cancel path during /review resilient to active-turn
races, while preserving any queued steer text by restoring it to the
composer.
- Add review-mode test coverage for the warning path.

## Testing

1. Start a `/review` with a diff large enough that the review stays
active for more than a few seconds.

2. While the review is still running, type a follow-up / steer message,
submit it, and then press `Esc`.
   Before: `Esc` causes the TUI to close abruptly.  
After: the review keeps running and the transcript shows a warning that
steer messages are not supported during `/review`, with guidance to use
`Ctrl+C` if you want to cancel.

3. Press `Ctrl+C` if you actually want to stop the review.  
Before: (after restarting the test since Pt. 2 crashed) this is the
intentional cancellation path.
After: this remains the intentional cancellation path, and any queued
follow-up steer text is restored to the composer instead of being lost.
   
## Note:
`/review` mode explicitly does not support steering at this time (as
noted in `turn_processer.rs`, if we want to explore that in the future
this code will need to be modified). This change keeps unsupported steer
attempts from crashing the TUI and preserves queued follow-up text if
the user cancels with Ctrl+C.
This commit is contained in:
canvrno-oai
2026-06-09 09:41:58 -07:00
committed by GitHub
Unverified
parent 6a9a49b334
commit 8a299fb704
7 changed files with 121 additions and 11 deletions
@@ -112,6 +112,20 @@ impl ChatWidget {
return;
}
const REVIEW_STEER_UNAVAILABLE_MESSAGE: &str = "Steer messages aren't supported during /review. Press Ctrl+C now to cancel the review.";
if self.chat_keymap.interrupt_turn.is_pressed(key_event)
&& self.review.is_review_mode
&& (!self.input_queue.pending_steers.is_empty()
|| !self.input_queue.rejected_steers_queue.is_empty())
&& self.bottom_pane.is_task_running()
&& self.bottom_pane.no_modal_or_popup_active()
&& !self.should_handle_vim_insert_escape(key_event)
{
self.add_warning_message(REVIEW_STEER_UNAVAILABLE_MESSAGE.to_string());
return;
}
if self.chat_keymap.interrupt_turn.is_pressed(key_event)
&& !self.input_queue.pending_steers.is_empty()
&& self.bottom_pane.is_task_running()
@@ -1,7 +1,7 @@
---
source: tui/src/chatwidget/tests/review_mode.rs
expression: lines_to_single_string(last)
assertion_line: 307
expression: last
---
⚠ Steer messages aren't supported during /review. Send your message after the
review finishes, or press Ctrl+C now to cancel the review.
⚠ Steer messages aren't supported during /review. Press Ctrl+C now to cancel the
review.
@@ -285,6 +285,29 @@ async fn steer_rejection_queues_review_follow_up_before_existing_queued_messages
}
}
#[tokio::test]
async fn esc_with_review_queued_steers_shows_warning_and_does_not_interrupt() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.thread_id = Some(ThreadId::new());
handle_turn_started(&mut chat, "turn-1");
handle_entered_review_mode(&mut chat, "feature branch");
let _ = drain_insert_history(&mut rx);
chat.input_queue
.pending_steers
.push_back(pending_steer("review follow-up"));
chat.refresh_pending_input_preview();
chat.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
assert!(!chat.input_queue.submit_pending_steers_after_interrupt);
assert_eq!(chat.input_queue.pending_steers.len(), 1);
assert_no_submit_op(&mut op_rx);
let cells = drain_insert_history(&mut rx);
let last = lines_to_single_string(cells.last().expect("review warning"));
assert_chatwidget_snapshot!("review_submission_warning_snapshot", last);
}
#[tokio::test]
async fn live_agent_message_renders_during_review_mode() {
let (mut chat, mut rx, _ops) = make_chatwidget_manual(/*model_override*/ None).await;