From 3166a5ba824ccb5ae849e0cbec6ecc84f9e67871 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 3 Mar 2026 09:19:37 +0000 Subject: [PATCH] fix: agent race (#13248) https://github.com/openai/codex/issues/13244 --- codex-rs/core/src/agent/control.rs | 62 ++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/codex-rs/core/src/agent/control.rs b/codex-rs/core/src/agent/control.rs index 6327e1e3e..85f28b4f6 100644 --- a/codex-rs/core/src/agent/control.rs +++ b/codex-rs/core/src/agent/control.rs @@ -405,18 +405,20 @@ impl AgentControl { }; let control = self.clone(); tokio::spawn(async move { - let mut status_rx = match control.subscribe_status(child_thread_id).await { - Ok(rx) => rx, - Err(_) => return, - }; - let mut status = status_rx.borrow().clone(); - while !is_final(&status) { - if status_rx.changed().await.is_err() { - status = control.get_status(child_thread_id).await; - break; + let status = match control.subscribe_status(child_thread_id).await { + Ok(mut status_rx) => { + let mut status = status_rx.borrow().clone(); + while !is_final(&status) { + if status_rx.changed().await.is_err() { + status = control.get_status(child_thread_id).await; + break; + } + status = status_rx.borrow().clone(); + } + status } - status = status_rx.borrow().clone(); - } + Err(_) => control.get_status(child_thread_id).await, + }; if !is_final(&status) { return; } @@ -1296,6 +1298,44 @@ mod tests { assert_eq!(wait_for_subagent_notification(&parent_thread).await, true); } + #[tokio::test] + async fn completion_watcher_notifies_parent_when_child_is_missing() { + let harness = AgentControlHarness::new().await; + let (parent_thread_id, parent_thread) = harness.start_thread().await; + let child_thread_id = ThreadId::new(); + + harness.control.maybe_start_completion_watcher( + child_thread_id, + Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn { + parent_thread_id, + depth: 1, + agent_nickname: None, + agent_role: Some("explorer".to_string()), + })), + ); + + assert_eq!(wait_for_subagent_notification(&parent_thread).await, true); + + let history_items = parent_thread + .codex + .session + .clone_history() + .await + .raw_items() + .to_vec(); + assert_eq!( + history_contains_text( + &history_items, + &format!("\"agent_id\":\"{child_thread_id}\"") + ), + true + ); + assert_eq!( + history_contains_text(&history_items, "\"status\":\"not_found\""), + true + ); + } + #[tokio::test] async fn spawn_thread_subagent_gets_random_nickname_in_session_source() { let harness = AgentControlHarness::new().await;