You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Side chat doesn't restore the parent thread's model_provider, so it fails with invalid_encrypted_content #46379
What version of the Codex App are you using (From “About Codex” dialog)?
26.911.61220
What subscription do you have?
Pro
What platform is your computer?
Darwin 25.6.0 arm64 arm
What issue are you seeing?
Opening a side chat on a thread created on the ChatGPT plan fails on the first message once the default provider is Azure:
{
"error": {
"message": "The encrypted content for item rs_...79a could not be verified. Reason: Encrypted content could not be decrypted or parsed.",
"type": "invalid_request_error",
"param": null,
"code": "invalid_encrypted_content"
}
}
The side chat's first request goes to the Azure endpoint (POST https://<resource>.openai.azure.com/openai/responses?api-version=2025-04-01-preview → 400 Bad Request). That request carries reasoning item rs_...79a, which the parent thread received from the ChatGPT backend.
What steps can reproduce the bug?
With model_provider = "openai" (ChatGPT sign-in), start a thread in Codex Desktop and run a few turns with a reasoning model so the history contains encrypted reasoning items.
Add a custom Azure provider (wire_api = "responses"), set model_provider = "azure", and restart the app.
Reopen the thread from step 1. It resumes on openai, as expected.
Open a side chat on that thread and send any message.
Result: the first turn fails with invalid_encrypted_content, and the request goes to the Azure endpoint.
What is the expected behavior?
A side chat should run on its parent thread's provider unless the client explicitly asks for a different one. This is the rule thread/resume followed since #19287. If the client does explicitly switch providers, encrypted reasoning and compaction items from the other provider should be dropped, not sent to an endpoint that can't decrypt them.
Current main looks like it already has the server-side inheritance behavior this report expects when the fork caller omits the provider override, which narrows the likely boundary to the side-chat client/request construction.
At 7498521d288b9b3b96ffba4eedf089d8d6e06a84, ThreadForkParams.modelProvider is optional, and the app-server thread_fork_creates_new_thread_and_emits_started regression creates a saved rollout with mock_provider, forks with ThreadForkParams { thread_id, thread_source, ..Default::default() }, and asserts the forked thread still has model_provider == "mock_provider".
But the current TUI side-conversation path is a useful bug-compatible precedent: fork_side_thread() goes through the shared fork builder, and thread_fork_params_from_config() unconditionally populates model_provider from the current config in embedded mode (and also populates model). That turns an ambient-default change into an explicit fork override, bypassing the app-server's persisted-provider inheritance.
So I would pin the regression at the real side-chat client boundary, not only in thread/fork: create a parent under provider A, change the ambient/default provider to B, then create a side chat through the actual side path and assert the outgoing fork request does not serialize modelProvider (or model) in inherit-saved mode and the returned fork remains on A. Keep a separate explicit-override case for B; if provider switching is intentional, that path should also prove provider-bound encrypted reasoning/compaction items are not replayed across providers.
That would distinguish “app-server failed to restore persisted provider” from “client accidentally asked app-server to override it,” and the public source currently points to the latter class.
Activity
84dnnvbdvp-debug commentedon Sep 18, 2026
Current
mainlooks like it already has the server-side inheritance behavior this report expects when the fork caller omits the provider override, which narrows the likely boundary to the side-chat client/request construction.At
7498521d288b9b3b96ffba4eedf089d8d6e06a84,ThreadForkParams.modelProvideris optional, and the app-serverthread_fork_creates_new_thread_and_emits_startedregression creates a saved rollout withmock_provider, forks withThreadForkParams { thread_id, thread_source, ..Default::default() }, and asserts the forked thread still hasmodel_provider == "mock_provider".But the current TUI side-conversation path is a useful bug-compatible precedent:
fork_side_thread()goes through the shared fork builder, andthread_fork_params_from_config()unconditionally populatesmodel_providerfrom the current config in embedded mode (and also populatesmodel). That turns an ambient-default change into an explicit fork override, bypassing the app-server's persisted-provider inheritance.So I would pin the regression at the real side-chat client boundary, not only in
thread/fork: create a parent under provider A, change the ambient/default provider to B, then create a side chat through the actual side path and assert the outgoing fork request does not serializemodelProvider(ormodel) in inherit-saved mode and the returned fork remains on A. Keep a separate explicit-override case for B; if provider switching is intentional, that path should also prove provider-bound encrypted reasoning/compaction items are not replayed across providers.That would distinguish “app-server failed to restore persisted provider” from “client accidentally asked app-server to override it,” and the public source currently points to the latter class.