Codex session replay can fail with invalid_encrypted_content from persisted encrypted reasoning/compaction records

Open 💬 5 comments Opened May 30, 2026 by MagicLuke
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

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, directory codex-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:

  • 554 top-level response_item rows with payload type: "reasoning" and an encrypted_content field.
  • 4 nested encrypted compaction records inside compacted.payload.replacement_history.
  • 28 malformed 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:

  1. Back up the rollout file byte-for-byte.
  2. Keep parseable JSONL records.
  3. Drop top-level encrypted reasoning records.
  4. Drop nested encrypted compaction/reasoning entries.
  5. Drop malformed/NUL/partial JSON rows.
  6. Verify no structured encrypted_content keys 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.

View original on GitHub ↗

5 Comments

github-actions[bot] contributor · 2 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #24369
  • #24582
  • #24191

Powered by Codex Action

Necmttn · 2 months ago

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 · 1 month ago

I hit a closely related provider-migration case on Codex CLI 0.142.x and was able to recover the visible session history locally.

The difference from #15219 is important: restoring the original model_provider is 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:

  1. Back up the full Codex home first.
  2. Scan state_5.sqlite for affected threads.model_provider values and collect their rollout_path values.
  3. Parse each rollout JSONL structurally.
  4. Preserve visible/user-actionable records: session_meta, user and assistant visible messages, tool calls, tool outputs, turn events, world-state/context records, timestamps, IDs, and file names.
  5. Drop replay-only response items whose payload type is reasoning, compaction, or context_compaction when they contain provider-bound encrypted replay content. Do not leave a reasoning item behind with encrypted_content removed, because that can turn the failure into Missing required parameter: input[N].encrypted_content.
  6. Rewrite the stored provider metadata only when the user explicitly chooses to migrate the session to another provider/model.
  7. Update the matching threads rows in a SQLite transaction, after making a database backup.
  8. Verify that affected rollouts no longer contain JSON keys named encrypted_content, that rollout files still parse, that state DB inventory matches disk, and that codex doctor is clean.
  9. Resume the session with the target provider and ask Codex to rebuild a handoff from the visible transcript.

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:

  • On resume/provider change, detect provider/account mismatch or replay failure with invalid_encrypted_content.
  • Offer a repair path that backs up first and sanitizes only provider-bound replay-only items.
  • Warn clearly that hidden reasoning/compaction state was discarded and visible history was preserved.
  • Avoid any blanket grep -v encrypted_content style workaround, because encrypted tool outputs/inter-agent payloads may be semantically different and should not be deleted blindly.

Relevant code areas from current main look like:

  • codex-rs/app-server/src/request_processors/thread_processor.rs: resume loads persisted rollout history and then calls resume_thread_with_history.
  • codex-rs/core/src/session/mod.rs and codex-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.rs and codex-rs/state/migrations/0001_threads.sql: threads.model_provider and rollout_path are 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 · 29 days ago

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_items and add a regression test.

shleder · 14 days ago

This is a strong persisted-history case: the visible conversation survived, but replay hit invalid_encrypted_content, and the rollout also contained 28 NUL/partial/malformed JSONL rows.

I’m testing Codex Rescue against exactly this boundary. It is local-first and will not delete encrypted records, guess their meaning, or modify the original rollout. The goal is to diagnose the durable corruption and produce a bounded handoff; verification stays fail-closed and may return REVIEW_REQUIRED.

If you kept the byte-for-byte backup from before manual sanitization, could you run:

pipx install codex-rescue==0.1.0a3
codex-rescue sessions
codex-rescue doctor --latest

Sanitized output is enough—please don’t share raw JSONL, encrypted content, databases, prompts, repository data, paths, tokens, or secrets.

https://github.com/shleder/codex-rescue