Skip to content

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

Open

Description

@MagicLuke

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.

Activity

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    contextIssues related to context management (including compaction)
    on May 30, 2026
  2. Necmttn commented on Jun 21, 2026

    @Necmttn

    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.

  3. Mhrnqaruni commented on Jul 6, 2026

    @Mhrnqaruni

    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.

  4. TyceHerrman commented on Jul 29, 2026

    @TyceHerrman

    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.

  5. flazouh commented on Oct 12, 2026

    @flazouh

    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 azure and openai for the same model), a thread can hold encrypted reasoning items from two providers. No single provider can decrypt both, so a later codex exec resume or codex exec fork fails 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIbugSomething isn't workingcontextIssues related to context management (including compaction)sessionIssues involving session (thread) management, resuming, forking, naming, archiving

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions