From ad57505ef5ca82a3ba5e182d01b27b572042079f Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Mon, 9 Mar 2026 11:22:51 -0700 Subject: [PATCH] Stabilize interrupted task approval cleanup (#14102) ## Summary - drain the active turn tasks before clearing pending approvals during interruption - keep the turn in hand long enough for interrupted tasks to observe cancellation first ## Why this fixes the flake Interrupted turns could clear pending approvals too early, which let an in-flight approval wait surface as a model-visible rejection before the turn emitted `TurnAborted`. Reordering the cleanup removes that race without changing the steady-state task model. --- codex-rs/core/src/tasks/mod.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/codex-rs/core/src/tasks/mod.rs b/codex-rs/core/src/tasks/mod.rs index a07e52f73..7bdcd5fbd 100644 --- a/codex-rs/core/src/tasks/mod.rs +++ b/codex-rs/core/src/tasks/mod.rs @@ -201,8 +201,13 @@ impl Session { } pub async fn abort_all_tasks(self: &Arc, reason: TurnAbortReason) { - for task in self.take_all_running_tasks().await { - self.handle_task_abort(task, reason.clone()).await; + if let Some(mut active_turn) = self.take_active_turn().await { + for task in active_turn.drain_tasks() { + self.handle_task_abort(task, reason.clone()).await; + } + // Let interrupted tasks observe cancellation before dropping pending approvals, or an + // in-flight approval wait can surface as a model-visible rejection before TurnAborted. + active_turn.clear_pending().await; } if reason == TurnAbortReason::Interrupted { self.close_unified_exec_processes().await; @@ -342,16 +347,9 @@ impl Session { *active = Some(turn); } - async fn take_all_running_tasks(&self) -> Vec { + async fn take_active_turn(&self) -> Option { let mut active = self.active_turn.lock().await; - match active.take() { - Some(mut at) => { - at.clear_pending().await; - - at.drain_tasks() - } - None => Vec::new(), - } + active.take() } pub(crate) async fn close_unified_exec_processes(&self) {