Client or integration
Codex App (native ChatGPT pool routing)
Area
Proxy and routing (account pool / thread affinity / encrypted reasoning replay)
Summary
With the ChatGPT account pool (codexAccountMode: pool) and two plus accounts registered, continuing an existing thread after pool selection moves to a different account fails with an upstream 400:
The encrypted content 6871...ef-0 could not be verified. Reason: Encrypted content could not be decrypted or parsed.
The request's historical reasoning items carry encrypted_content minted by the account that produced them (stored server-side on that account). When the pool routes the thread to a different account - after adding a new account (it becomes selected), or after the service restarts and in-memory thread affinity is lost - the new account's backend cannot verify the old account's ciphertext and rejects the whole request.
Reproduction
- opencodex 2.28.0, codexAccountMode: pool, one existing plus account (main), start a conversation (any model, e.g. gpt-5.6-luna)
- Add a second plus account (account pool) - the new account becomes selected
- Continue the SAME thread in the Codex app (do not start a new session)
- Request is routed to the new account namespace (openai-); upstream returns 400 Encrypted content could not be decrypted or parsed
Request log (redacted):
{
"provider": "openai-pe17f2a",
"admissionKind": "loopback",
"inboundProtocol": "responses",
"accountLogLabel": "pe17f2a",
"requestedModel": "gpt-5.6-luna",
"status": 400,
"errorCode": "invalid_request_error",
"upstreamError": "The encrypted content 6871...ef-0 could not be verified. Reason: Encrypted content could not be decrypted or parsed."
}
A brand-new session on the newly selected account works fine (first turn has no historical ciphertext).
Root cause analysis
- Native ChatGPT responses carry encrypted_content on reasoning output items; the ciphertext is account-bound (server-side key/storage).
- Codex threads replay history verbatim, so a continued thread sends the previous account's encrypted_content back upstream.
- Pool affinity (resolveCodexAccountForThreadDetailed, CodexThreadAffinityExpiredError) keeps threads on their original account while the in-memory binding survives - but adding an account / restarting the proxy can re-select (ocx account current shows the new account as selected) or drop the binding, so the thread's next request is sent with the OTHER account's credentials and history.
- The affinity-expired path returns 409 start a new session, but in this flow the request is admitted and forwarded, and the failure surfaces as an opaque upstream 400 instead.
Suggested fix
- Persist thread-to-account affinity across restarts (and across account-pool membership changes) so existing threads never silently switch accounts.
- Defense in depth: when pool routing selects an account different from the one that produced the thread's historical encrypted_content items, either refuse with the existing 409 (Codex thread account affinity expired; start a new session) or scrub the account-bound ciphertext from the forwarded history.
Version
2.28.0
Operating system
macOS
Client or integration
Codex App (native ChatGPT pool routing)
Area
Proxy and routing (account pool / thread affinity / encrypted reasoning replay)
Summary
With the ChatGPT account pool (codexAccountMode: pool) and two plus accounts registered, continuing an existing thread after pool selection moves to a different account fails with an upstream 400:
The request's historical reasoning items carry encrypted_content minted by the account that produced them (stored server-side on that account). When the pool routes the thread to a different account - after adding a new account (it becomes selected), or after the service restarts and in-memory thread affinity is lost - the new account's backend cannot verify the old account's ciphertext and rejects the whole request.
Reproduction
Request log (redacted):
{ "provider": "openai-pe17f2a", "admissionKind": "loopback", "inboundProtocol": "responses", "accountLogLabel": "pe17f2a", "requestedModel": "gpt-5.6-luna", "status": 400, "errorCode": "invalid_request_error", "upstreamError": "The encrypted content 6871...ef-0 could not be verified. Reason: Encrypted content could not be decrypted or parsed." }A brand-new session on the newly selected account works fine (first turn has no historical ciphertext).
Root cause analysis
Suggested fix
Version
2.28.0
Operating system
macOS