From 97da50c1aa4169cb6bab68d7e4c9c7e7cd2c842f Mon Sep 17 00:00:00 2001 From: sayan-oai Date: Thu, 24 Sep 2026 23:55:22 +0000 Subject: [PATCH] Expose current environment selections for a running turn (#47970) ## Why `active_turn_environment_selections()` returns the selections captured at turn start, so it does not reflect later environment settings updates. ## What changed Add `CodexThread::current_turn_environment_selections(expected_turn_id)` to read the running turn's current selections, including environments that are still starting or have failed. Return `None` when the requested turn is no longer active or has been cancelled. ## Testing Add a regression test covering selection updates, mismatched turn IDs, and completed turns, while verifying that the initial selection snapshot remains unchanged. GitOrigin-RevId: bb526a686d549209142150ffde99ad1444add205 --- codex-rs/core/src/codex_thread.rs | 14 +++++ .../step_settings/environment_selection.rs | 58 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index 9d2c3adb43..4812c2b056 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -546,6 +546,20 @@ impl CodexThread { Some(task.turn_context.initial_environments.all_selections()) } + /// Returns the named running turn's current selections, including environments that are + /// still starting or have failed. Returns `None` if that turn is no longer running. + pub async fn current_turn_environment_selections( + &self, + expected_turn_id: &str, + ) -> Option> { + let active = self.session.active_turn.lock().await; + let task = active.as_ref()?.task.as_ref()?; + if task.turn_context.sub_id != expected_turn_id || task.cancellation_token.is_cancelled() { + return None; + } + Some(self.session.services.turn_environments.selections()) + } + /// Captures a regular turn only after its input is recorded. The caller must flush the rollout. pub async fn interrupted_turn( &self, diff --git a/codex-rs/core/tests/suite/step_settings/environment_selection.rs b/codex-rs/core/tests/suite/step_settings/environment_selection.rs index f2fa41b82e..b758198396 100644 --- a/codex-rs/core/tests/suite/step_settings/environment_selection.rs +++ b/codex-rs/core/tests/suite/step_settings/environment_selection.rs @@ -10,6 +10,64 @@ use core_test_support::submit_thread_settings; use core_test_support::test_codex::environment_config_for_selection; use pretty_assertions::assert_eq; +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn current_turn_environment_selections_follow_active_updates() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + mount_sse_sequence( + &server, + vec![paused_response("first", "pause"), sse_completed("second")], + ) + .await; + let test = step_settings_test().build_with_auto_env(&server).await?; + let thread = &test.codex; + let turn_id = start_paused_turn(thread).await?.turn_id; + let initial = thread + .active_turn_environment_selections() + .await + .expect("running turn"); + assert_eq!(initial.len(), 1); + assert_eq!( + thread.current_turn_environment_selections(&turn_id).await, + Some(initial.clone()) + ); + assert_eq!( + thread + .current_turn_environment_selections("other-turn") + .await, + None + ); + + for environments in [vec![], initial.clone()] { + apply_turn_settings( + thread, + &turn_id, + TurnSettingsUpdate { + environments: Some(environments.clone()), + ..Default::default() + }, + ) + .await?; + assert_eq!( + thread.current_turn_environment_selections(&turn_id).await, + Some(environments) + ); + assert_eq!( + thread.active_turn_environment_selections().await, + Some(initial.clone()) + ); + } + + answer_paused_turn(thread, &turn_id).await?; + wait_for_event(thread, |event| matches!(event, EventMsg::TurnComplete(_))).await; + assert_eq!( + thread.current_turn_environment_selections(&turn_id).await, + None + ); + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn model_update_preserves_active_environment_and_next_turn_uses_new_selection() -> Result<()> {