From bce74c70ce058982534507330ff33f7b196708ef Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sat, 25 Apr 2026 12:40:00 -0700 Subject: [PATCH 1/2] Restore persisted model provider on thread resume (#19287) Fixes #15219. ## Why `thread/resume` should continue a persisted thread with the same model provider that created the thread. The app server already restores the persisted model and reasoning effort before resuming, but it was leaving `model_provider` unset. If a user created a thread with one provider and later switched their active profile to another provider, resumed encrypted history could be sent to the wrong endpoint and fail with `invalid_encrypted_content`. The thread metadata already records the original provider, so resume should apply it when the caller has not explicitly requested a different model/provider/reasoning configuration. ## What changed This updates `merge_persisted_resume_metadata` in `app-server/src/codex_message_processor.rs` to copy `ThreadMetadata::model_provider` into `ConfigOverrides::model_provider` alongside the persisted model. The existing resume metadata tests now also assert that: - the persisted provider is restored for normal resume - explicit model, provider, or reasoning-effort overrides still prevent persisted resume metadata from being applied - a thread with no persisted model or reasoning effort still resumes with its persisted provider ## Verification - `cargo test -p codex-app-server` passed the app-server unit tests, including the updated resume metadata coverage. The broader integration portion of that command failed in an unrelated environment-sensitive skills-budget warning assertion, where this run saw 8 omitted skills instead of the expected 7. - `just fix -p codex-app-server` completed successfully. --- codex-rs/app-server/src/codex_message_processor.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 4d31fd1eaf..cddc5d5856 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -9294,6 +9294,7 @@ fn merge_persisted_resume_metadata( } typesafe_overrides.model = persisted_metadata.model.clone(); + typesafe_overrides.model_provider = Some(persisted_metadata.model_provider.clone()); if let Some(reasoning_effort) = persisted_metadata.reasoning_effort { request_overrides.get_or_insert_with(HashMap::new).insert( @@ -10983,6 +10984,10 @@ mod tests { typesafe_overrides.model, Some("gpt-5.1-codex-max".to_string()) ); + assert_eq!( + typesafe_overrides.model_provider, + Some("mock_provider".to_string()) + ); assert_eq!( request_overrides, Some(HashMap::from([( @@ -11013,6 +11018,7 @@ mod tests { ); assert_eq!(typesafe_overrides.model, Some("gpt-5.2-codex".to_string())); + assert_eq!(typesafe_overrides.model_provider, None); assert_eq!( request_overrides, Some(HashMap::from([( @@ -11041,6 +11047,7 @@ mod tests { ); assert_eq!(typesafe_overrides.model, None); + assert_eq!(typesafe_overrides.model_provider, None); assert_eq!( request_overrides, Some(HashMap::from([( @@ -11092,6 +11099,7 @@ mod tests { ); assert_eq!(typesafe_overrides.model, None); + assert_eq!(typesafe_overrides.model_provider, None); assert_eq!( request_overrides, Some(HashMap::from([( @@ -11116,6 +11124,10 @@ mod tests { ); assert_eq!(typesafe_overrides.model, None); + assert_eq!( + typesafe_overrides.model_provider, + Some("mock_provider".to_string()) + ); assert_eq!(request_overrides, None); Ok(()) } From 6fe18bbbff0a3ec2b6f8b9acd6bd2ad1703f9cb8 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Sat, 25 Apr 2026 13:26:44 -0700 Subject: [PATCH 2/2] test: isolate remote thread store persistence regression --- codex-rs/app-server/tests/suite/v2/remote_thread_store.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/codex-rs/app-server/tests/suite/v2/remote_thread_store.rs b/codex-rs/app-server/tests/suite/v2/remote_thread_store.rs index ebee1fd7c1..7556f4cd14 100644 --- a/codex-rs/app-server/tests/suite/v2/remote_thread_store.rs +++ b/codex-rs/app-server/tests/suite/v2/remote_thread_store.rs @@ -54,6 +54,8 @@ async fn thread_start_with_non_local_thread_store_does_not_create_local_persiste let server = create_mock_responses_server_repeating_assistant("Done").await; let codex_home = TempDir::new()?; let store_id = Uuid::new_v4().to_string(); + // Plugin startup warmups may create `.tmp` under codex_home. Disable them + // here so this regression stays focused on thread persistence artifacts. create_config_toml_with_thread_store(codex_home.path(), &server.uri(), &store_id)?; let loader_overrides = LoaderOverrides::without_managed_config_for_tests(); @@ -248,6 +250,9 @@ base_url = "{server_uri}/v1" wire_api = "responses" request_max_retries = 0 stream_max_retries = 0 + +[features] +plugins = false "# ), )