diff --git a/codex-rs/app-server/src/request_processors/turn_processor.rs b/codex-rs/app-server/src/request_processors/turn_processor.rs index 90dcbfb0e3..61bda506c2 100644 --- a/codex-rs/app-server/src/request_processors/turn_processor.rs +++ b/codex-rs/app-server/src/request_processors/turn_processor.rs @@ -519,21 +519,16 @@ impl TurnRequestProcessor { }, ) .await?; - let after_turn_context = if resolved_overrides.has_any_overrides { - Some(thread_turn_context_from_snapshot( - &thread - .preview_turn_context_overrides(resolved_overrides.overrides.clone()) - .await - .map_err(|err| { - invalid_request(format!("invalid turn context override: {err}")) - })?, - )) - } else { - None - }; + let has_turn_context_overrides = resolved_overrides.has_any_overrides; + if has_turn_context_overrides { + thread + .preview_turn_context_overrides(resolved_overrides.overrides.clone()) + .await + .map_err(|err| invalid_request(format!("invalid turn context override: {err}")))?; + } // Start the turn by submitting the user input. Return its submission id as turn_id. - let turn_op = if resolved_overrides.has_any_overrides { + let turn_op = if has_turn_context_overrides { let overrides = resolved_overrides.overrides; Op::UserInputWithTurnContext { items: mapped_items, @@ -571,29 +566,27 @@ impl TurnRequestProcessor { error })?; - if let Some(mut after_turn_context) = after_turn_context { - if before_turn_context != after_turn_context { - let thread_state = self.thread_state_manager.thread_state(thread_id).await; - let turn_started = { - let mut thread_state = thread_state.lock().await; - thread_state.turn_started_receiver(&turn_id) - }; - if let Some(turn_started) = turn_started { - // Bound how long the RPC waits for the core turn-start acknowledgement. - tokio::time::timeout(TURN_STARTED_ACK_TIMEOUT, turn_started) - .await - .map_err(|_| { - internal_error( - "timed out waiting for turn context overrides to apply".to_string(), - ) - })? - .map_err(|_| { - internal_error("turn context override waiter was cancelled".to_string()) - })?; - } - after_turn_context = - thread_turn_context_from_snapshot(&thread.config_snapshot().await); + if has_turn_context_overrides { + let thread_state = self.thread_state_manager.thread_state(thread_id).await; + let turn_started = { + let mut thread_state = thread_state.lock().await; + thread_state.turn_started_receiver(&turn_id) + }; + if let Some(turn_started) = turn_started { + // Bound how long the RPC waits for the core turn-start acknowledgement. + tokio::time::timeout(TURN_STARTED_ACK_TIMEOUT, turn_started) + .await + .map_err(|_| { + internal_error( + "timed out waiting for turn context overrides to apply".to_string(), + ) + })? + .map_err(|_| { + internal_error("turn context override waiter was cancelled".to_string()) + })?; } + let after_turn_context = + thread_turn_context_from_snapshot(&thread.config_snapshot().await); self.maybe_emit_turn_context_updated( ¶ms.thread_id, &before_turn_context, diff --git a/codex-rs/app-server/tests/suite/v2/thread_turn_context.rs b/codex-rs/app-server/tests/suite/v2/thread_turn_context.rs index 5c6f4f3ad4..8f1bb998b4 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_turn_context.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_turn_context.rs @@ -252,8 +252,9 @@ async fn turn_start_emits_turn_context_updated_when_overrides_change_defaults() Ok(()) } -#[tokio::test] -async fn thread_turn_context_update_after_turn_start_preserves_newer_update() -> Result<()> { +async fn assert_newer_update_survives_turn_start( + turn_start_overrides: TurnStartParams, +) -> Result<()> { let server = create_mock_responses_server_sequence_unchecked(vec![ create_final_assistant_message_sse_response("Done")?, ]) @@ -272,9 +273,7 @@ async fn thread_turn_context_update_after_turn_start_preserves_newer_update() -> text: "Hello".to_string(), text_elements: Vec::new(), }], - model: Some("gpt-5.2".to_string()), - effort: Some(ReasoningEffort::Low), - ..Default::default() + ..turn_start_overrides }) .await?; let update_request_id = mcp @@ -330,3 +329,23 @@ async fn thread_turn_context_update_after_turn_start_preserves_newer_update() -> Ok(()) } + +#[tokio::test] +async fn thread_turn_context_update_after_turn_start_preserves_newer_update() -> Result<()> { + assert_newer_update_survives_turn_start(TurnStartParams { + model: Some("gpt-5.2".to_string()), + effort: Some(ReasoningEffort::Low), + ..Default::default() + }) + .await +} + +#[tokio::test] +async fn thread_turn_context_update_after_no_op_turn_start_override_preserves_newer_update() +-> Result<()> { + assert_newer_update_survives_turn_start(TurnStartParams { + model: Some("mock-model".to_string()), + ..Default::default() + }) + .await +}