`codex exec resume` fails with 'Missing required parameter: input[N].encrypted_content' after internal 'thread not found' (rollout intact)
What version of Codex CLI is running?
0.125.0
What subscription do you have?
ChatGPT Pro (via self-hosted local gateway forwarding to chatgpt.com/backend-api; wire_api = "responses")
Which model were you using?
gpt-5.4
What platform is your computer?
macOS 14 (Darwin 25.3.0) arm64
What terminal emulator and version are you using (if applicable)?
Ghostty / iTerm2; also reproduces from paseo (third-party MCP host that drives codex app-server).
What issue are you seeing?
Resuming a previously-saved session fails immediately with:
{
"error": {
"message": "Missing required parameter: 'input[3].encrypted_content'.",
"type": "invalid_request_error",
"param": "input[3].encrypted_content",
"code": "missing_required_parameter"
}
}
…and is accompanied by this internal CLI log right after the failure:
ERROR codex_core::session: failed to record rollout items: thread 019dc8d9-3ff9-7f23-bb45-54818d3b3d9c not found
This is distinct from the related encrypted-content issues already on file:
| Issue | Trigger | Server error code | Field state |
|---|---|---|---|
| #15219 (CLOSED) | resume across model_provider profiles | invalid_encrypted_content | field present, can't decrypt |
| #17541 | mid-conversation turn/start model switch | invalid_encrypted_content | field present, can't decrypt |
| #10506 | (unclear) | string too long on input[N].encrypted_content | field present, malformed |
| This issue | resume of own session, same provider throughout, rollout file intact | missing_required_parameter | field absent from request |
The rollout file on disk is fully healthy — every reasoning item has encrypted_content. The bug is that the CLI's resume reconstruction path omits the field when re-emitting input[] after the internal thread not found condition fires.
What steps can reproduce the bug?
Minimal reproduction (no paseo involved — bare codex exec resume):
codex exec --skip-git-repo-check resume <session-uuid> "Reply with PONG and stop."
For me, any session whose internal thread index entry has gone missing reproduces 100% on the first turn, regardless of model, prompt, or working directory. In my case the trigger appears to be sessions originally created by paseo calling codex app-server — those rollouts get written but no matching thread index entry seems to be created (or is later evicted), so subsequent codex exec resume of the same sessionId always trips this.
What is the expected behavior?
Either:
- Preferred: When the thread index lookup misses, hydrate the missing reasoning items (including
encrypted_content) from the rollout file before constructing theResponsesApiRequest.input. Resume then succeeds. - Fallback: If hydration is impossible, refuse to send a malformed request — surface a clear "thread index missing, cannot resume" error instead of forwarding a request guaranteed to be rejected by the server.
Today the request is built with the field omitted and sent anyway, producing a 400 every turn.
Additional information
Evidence the rollout file is healthy
For the failing sessionId 019dc8d9-3ff9-7f23-bb45-54818d3b3d9c, I audited every reasoning item in the on-disk rollout (~/.codex/sessions/2026/04/26/rollout-2026-04-26T16-12-54-019dc8d9-...jsonl, 1.1 MB, 504 lines):
reasoning items: 44
with encrypted_content: 44 (length min/max: 996 / 21516 bytes)
without encrypted_content: 0
So the persistence layer is fine. The drop happens between rollout and request body construction, in the resume path.
Why I think it's the "thread not found" fallback
The error log
ERROR codex_core::session: failed to record rollout items: thread 019dc8d9-... not found
appears immediately before the server-side rejection in stderr, every time. Same sessionId works perfectly the first time it's used (before whatever indexing condition trips); only resume of a session whose internal thread record is missing reproduces.
This looks like a sibling of #16271 (orphaned threads → crash/resume loop) and #16872 (app-server rollout never materialized → "no rollout found"), but inverted: in those issues thread-record exists / rollout missing; here rollout exists / thread-record missing. Both stem from the dual-storage (thread index + rollout file) drifting out of sync, but the consequence in this code path is silently dropping encrypted_content rather than raising.
Workaround
Don't codex exec resume affected sessions. Start a new session and feed the prior context in as a prompt. (Confirmed clean — a fresh sessionId with the same model/provider/CLI version works, since reconstruction is not invoked.)
Suggested investigation pointers
codex_core::session::record_rollout_items(the function emitting thethread <id> not foundERROR) — check whether the same lookup miss also feeds the input-builder downstream.- Whatever code transforms loaded rollout entries into
ResponsesApiRequest.inputfor the resume case — verify it preservesencrypted_contentonReasoningitems unconditionally rather than only when the thread index hit succeeds. - Confirm whether sessions created via
codex app-server(no interactive UI) consistently register thread records, or whether the registration is tied to interactive resume / app paths.
Happy to provide rollout file excerpts or run additional probes if useful.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗