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.
This commit is contained in:
Eric Traut
2026-04-25 12:40:00 -07:00
committed by GitHub
Unverified
parent 88f300d74d
commit bce74c70ce
@@ -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(())
}