Repository navigation
Codex session replay can fail with invalid_encrypted_content from persisted encrypted reasoning/compaction records #25290
Description
Summary
A Codex CLI session under a local repo became impossible to continue because the API rejected persisted encrypted reasoning content during conversation replay:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"code": "invalid_encrypted_content",
"message": "The encrypted content gAAA... could not be verified. Reason: Encrypted content could not be decrypted or parsed."
},
"status": 400
}The user-visible conversation messages were still present locally, but replay failed before the agent could continue.
Environment
- Codex CLI:
codex-cli 0.135.0 - Install method: npm package
@openai/codex - Package repository metadata:
git+https://github.com/openai/codex.git, directorycodex-cli - OS: Red Hat Enterprise Linux 9.4 x86_64
- Auth mode: ChatGPT tokens
- Session file pattern:
$CODEX_HOME/sessions/YYYY/MM/DD/rollout-*.jsonl
What I found in the affected session file
The affected repo-local rollout file had a valid session_meta and most user/assistant events were intact, but it also contained corrupt replay material:
554top-levelresponse_itemrows with payloadtype: "reasoning"and anencrypted_contentfield.4nested encryptedcompactionrecords insidecompacted.payload.replacement_history.28malformed JSONL rows, including NUL-byte rows and partial/truncated JSON around the later part of the file.
After removing only those invalid/encrypted replay-only records, the session JSONL parsed again and the user-visible final turns remained available.
Expected behavior
Codex should not fail the whole continuation request when persisted local history contains encrypted reasoning or encrypted compaction blobs that the current client/server can no longer decrypt or parse.
A safer behavior would be one of:
- skip/drop invalid encrypted reasoning or compaction records during replay,
- fall back to visible
message,event_msg, and tool-output records, - detect and repair/ignore malformed JSONL rows before constructing API input,
- show a local recovery prompt instead of sending invalid encrypted content to the API.
Actual behavior
The continuation failed with invalid_encrypted_content from the API. The user had to manually sanitize the rollout JSONL file before the conversation could be recovered.
Notes
I am not attaching raw rollout history because it can contain private prompts, tool outputs, and encrypted model state. The practical workaround was:
- Back up the rollout file byte-for-byte.
- Keep parseable JSONL records.
- Drop top-level encrypted reasoning records.
- Drop nested encrypted compaction/reasoning entries.
- Drop malformed/NUL/partial JSON rows.
- Verify no structured
encrypted_contentkeys remain and that final user-visible messages are still present.
This seems like a good candidate for a built-in codex doctor or codex repair-history check as well.
Activity
github-actions commentedon May 30, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Necmttn commentedon Jun 21, 2026
Replay should be able to sanitize unverifiable encrypted reasoning without losing the visible session.
If encrypted reasoning or compacted state fails verification, mark that record degraded, drop it from request replay, and keep user messages, assistant visible text, tool calls, and tool results. Resume can warn that hidden reasoning was discarded while preserving the actionable transcript.
Generated with ax.
Mhrnqaruni commentedon Jul 6, 2026
I hit a closely related provider-migration case on Codex CLI
0.142.xand was able to recover the visible session history locally.The difference from #15219 is important: restoring the original
model_provideris correct when that provider/account is still usable, but it does not help when the original provider path is gone and the user needs to continue under a different provider. In that case the old encrypted reasoning/compaction payloads are provider/account-bound replay data, and sending them to the new provider fails before the visible transcript can continue.The recovery flow that worked:
state_5.sqlitefor affectedthreads.model_providervalues and collect theirrollout_pathvalues.session_meta, user and assistant visible messages, tool calls, tool outputs, turn events, world-state/context records, timestamps, IDs, and file names.reasoning,compaction, orcontext_compactionwhen they contain provider-bound encrypted replay content. Do not leave areasoningitem behind withencrypted_contentremoved, because that can turn the failure intoMissing required parameter: input[N].encrypted_content.threadsrows in a SQLite transaction, after making a database backup.encrypted_content, that rollout files still parse, that state DB inventory matches disk, and thatcodex doctoris clean.In my test this recovered dozens of provider-migrated sessions, including a large rollout with thousands of encrypted reasoning items. The hidden reasoning was intentionally discarded; the visible conversation and tool history remained available for continuity.
I think the durable CLI behavior should be something like:
invalid_encrypted_content.grep -v encrypted_contentstyle workaround, because encrypted tool outputs/inter-agent payloads may be semantically different and should not be deleted blindly.Relevant code areas from current
mainlook like:codex-rs/app-server/src/request_processors/thread_processor.rs: resume loads persisted rollout history and then callsresume_thread_with_history.codex-rs/core/src/session/mod.rsandcodex-rs/core/src/session/rollout_reconstruction.rs: resumed rollout items are reconstructed into model-visible history.codex-rs/protocol/src/models.rs:ResponseItem::{Reasoning, Compaction, ContextCompaction}carry the encrypted replay payloads.codex-rs/state/src/runtime/threads.rsandcodex-rs/state/migrations/0001_threads.sql:threads.model_providerandrollout_pathare the DB metadata involved.codex-rs/cli/src/state_db_recovery.rs: existing backup/recovery patterns for local DB damage.I can share a sanitized recovery script or help turn this into a maintainer-approved design/PR if that would be useful.
TyceHerrman commentedon Jul 29, 2026
I found a concrete replay failure path that might also explain some cases here.
During
thread/inject_items, encrypted reasoning or compaction items with missing IDs can receive newly synthesized IDs. The encrypted payload stays bound to its original item ID, so replay can fail verification.My observed server error said
Encrypted content item_id did not match the target item id.This issue reports the more general decrypt or parse reason, so I can't confirm they share the same cause.It'd be useful to check whether the affected rollout contained encrypted items without IDs or whether IDs were reassigned during replay. A fix for this path would avoid synthesizing IDs for encrypted reasoning and compaction items during
thread/inject_itemsand add a regression test.fix(ai): stop replaying encrypted reasoning blobs to Codex backend
flazouh commentedon Oct 12, 2026
Another way to reach this state, and a proposal for the fix.
When Codex runs against a gateway that serves the model from more than one upstream provider (we see
azureandopenaifor the same model), a thread can hold encrypted reasoning items from two providers. No single provider can decrypt both, so a latercodex exec resumeorcodex exec forkfails with HTTP 400 "Your request contains encrypted reasoning or compaction content from multiple providers". The visible transcript is fine. Only the replayed reasoning items are bad.Proposal, in line with the comment above from @Necmttn: when the API rejects stored reasoning with
invalid_encrypted_content(or a mixed-provider error), drop the reasoning and compaction items that the request cannot verify and retry the turn once, keeping user messages, assistant text, tool calls and tool results. A warning that hidden reasoning was dropped is enough. This is the only fix that works for threads that already exist.