diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 9b88c24495..0121a1f41c 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -5816,8 +5816,13 @@ impl ChatWidget { else { unreachable!("user message item should convert to a user message event"); }; - let compare_key = Self::pending_steer_compare_key_from_items(&user_message.content); - self.on_user_message_event_reconciling_pending_steer(event, compare_key); + if from_replay { + self.on_user_message_event(event); + } else { + let compare_key = + Self::pending_steer_compare_key_from_items(&user_message.content); + self.on_user_message_event_reconciling_pending_steer(event, compare_key); + } } ThreadItem::AgentMessage { id, @@ -6240,8 +6245,13 @@ impl ChatWidget { } } else if from_replay { self.last_non_retry_error = None; - self.finalize_turn(); - self.request_redraw(); + if !matches!( + notification.error.codex_error_info, + Some(AppServerCodexErrorInfo::ActiveTurnNotSteerable { .. }) + ) { + self.finalize_turn(); + self.request_redraw(); + } } else { self.last_non_retry_error = Some(( notification.turn_id.clone(), @@ -6769,8 +6779,13 @@ impl ChatWidget { codex_error_info, }) => { if from_replay { - self.finalize_turn(); - self.request_redraw(); + if !matches!( + codex_error_info, + Some(CoreCodexErrorInfo::ActiveTurnNotSteerable { .. }) + ) { + self.finalize_turn(); + self.request_redraw(); + } } else if codex_error_info .as_ref() .is_some_and(|info| self.handle_steer_rejected_error(info)) @@ -6940,7 +6955,7 @@ impl ChatWidget { } EventMsg::ItemCompleted(event) => { let item = event.item; - if let codex_protocol::items::TurnItem::UserMessage(item) = &item { + if !from_replay && let codex_protocol::items::TurnItem::UserMessage(item) = &item { let EventMsg::UserMessage(event) = item.as_legacy_event() else { unreachable!("user message item should convert to a legacy user message"); }; diff --git a/codex-rs/tui/src/chatwidget/tests/review_mode.rs b/codex-rs/tui/src/chatwidget/tests/review_mode.rs index 26f10ee161..9b95e4d01a 100644 --- a/codex-rs/tui/src/chatwidget/tests/review_mode.rs +++ b/codex-rs/tui/src/chatwidget/tests/review_mode.rs @@ -468,7 +468,7 @@ async fn unacknowledged_pending_steer_is_retried_as_follow_up_when_turn_complete } #[tokio::test] -async fn replayed_user_message_commit_clears_pending_steer_without_retry() { +async fn replayed_completion_does_not_retry_pending_steer() { let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; chat.thread_id = Some(ThreadId::new()); chat.on_task_started(); @@ -499,26 +499,7 @@ async fn replayed_user_message_commit_clears_pending_steer_without_retry() { assert_eq!(chat.pending_steers.len(), 1); assert_no_submit_op(&mut op_rx); - chat.replay_thread_turns( - vec![AppServerTurn { - id: "turn-with-commit".to_string(), - items: vec![AppServerThreadItem::UserMessage { - id: "user-1".to_string(), - content: vec![AppServerUserInput::Text { - text: "already committed".to_string(), - text_elements: Vec::new(), - }], - }], - status: AppServerTurnStatus::Completed, - error: None, - started_at: None, - completed_at: None, - duration_ms: None, - }], - ReplayKind::ThreadSnapshot, - ); - - assert!(chat.pending_steers.is_empty()); + assert_eq!(chat.pending_steers.len(), 1); assert!(chat.rejected_steers_queue.is_empty()); assert_no_submit_op(&mut op_rx); }