[CLI][custom provider] Type-invalid item_ reasoning IDs survive replay validation and OpenAI expects rs_
What version of Codex CLI is running?
codex-cli 0.148.0-alpha.9 (Codex Desktop 26.810.7004.0, Windows x64)
What subscription do you have?
ChatGPT subscription through Codex Desktop. The defect is in request serialization and reproduces independently of the subscription tier.
Which model were you using?
Responses API models. The failure is independent of the selected model.
What platform is your computer?
Windows 11 x64
What terminal emulator and version are you using (if applicable)?
Codex Desktop; PowerShell 7 was used for source-level verification.
Codex doctor report
not included: the report contains unrelated local configuration; the source-level reproduction and regression tests below isolate the failure
What issue are you seeing?
When a local task contains response items produced by a custom Responses-compatible provider and the task is subsequently continued using OpenAI authentication, Codex can send the custom provider's generic item_... ID back to the OpenAI Responses API.
OpenAI rejects the request:
[ApiIdParam] [input[292].id] [invalid_id_prefix]
Invalid 'input[292].id': 'item_REDACTED'. Expected an ID that begins with 'rs'.
The problem is in ModelClient::prepare_response_items_for_request() in codex-rs/core/src/client.rs. It currently checks only:
if item.id().is_some_and(|id| !id.is_prefixed()) {
item.set_id(None);
}
ResponseItemId::is_prefixed() accepts any non-empty prefix and suffix, so item_foreign-reasoning is considered valid. However, the API validates IDs by response-item type: reasoning requires rs_, messages require msg_, function calls require fc_, and so on.
The same implementation remains present on current main at commit 9ded177ce7c1c0bd2047f902936c177612ab3434 (checked 2026-08-16).
This is not the UUID/local-message case in #32282 or #33395, and it is not the Azure store=false persistence issue in #35739. Here the ID is syntactically prefixed, but its prefix belongs to no valid API item type.
What steps can reproduce the bug?
- Configure a custom Responses-compatible provider.
- Start a task and let the provider return persisted response items such as:
``json``
{
"type": "reasoning",
"id": "item_foreign-reasoning",
"summary": [],
"encrypted_content": "foreign-ciphertext"
}
A custom provider may also return message or function-call items with generic item_... IDs.
- Preserve the same local task history and switch the active route/authentication to OpenAI.
- Continue the task with another user message.
prepare_response_items_for_request()retainsitem_foreign-reasoningbecauseis_prefixed()returns true.- The OpenAI Responses API rejects the request with
invalid_id_prefix, expectingrs_.
A deterministic source-level regression fixture can append the foreign reasoning item above, plus a message and function call using item_..., to the existing HTTP and WebSocket client request tests. Both transports reproduce the incorrect serialization before the fix.
What is the expected behavior?
Outgoing response item IDs should be checked against the prefix required by their concrete ResponseItem type, not merely checked for the presence of any prefix.
For portable items such as messages and function calls, Codex should omit a type-invalid ID from the request copy while preserving content, call_id, and outputs. For foreign reasoning or compaction items that carry provider-specific opaque state, Codex should omit the item from the cross-provider request rather than replaying undecryptable ciphertext.
The persisted local prompt/history should remain unchanged.
Additional information
A validated fix against the exact rust-v0.148.0-alpha.9 tag does the following:
- Adds
ResponseItemId::is_prefixed_with(expected_prefix). - Uses
ResponseItem::id_prefix()during outbound request preparation. - Removes reasoning/compaction items whose IDs have a type-invalid prefix from the request copy.
- Clears type-invalid IDs from portable message/tool items while preserving their content and linkage fields.
- Restores original IDs after WebSocket request serialization so in-memory and persisted prompt state are not mutated.
Focused regression tests passed for:
- HTTP Responses request serialization;
- Responses-over-WebSocket serialization and prompt non-mutation;
- type-specific prefix validation.
The observed task history, prompts, tool output, and encrypted fields were not attached to this report. The synthetic values above are sufficient to reproduce the defect without exposing user data or credentials.
7 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Thanks for the cross-reference. This is related to #38365 but not a duplicate.
#38365 requests a provider-neutral handoff/fork workflow and reports tool call/output ordering differences. This issue is a narrower request-serialization bug on the normal replay path:
prepare_response_items_for_request()already attempts to sanitize invalid IDs;ResponseItemId::is_prefixed()predicate;item_foreign-reasoningtherefore passes validation even though the concreteResponseItem::Reasoningcontract requiresrs_;The bug has a bounded regression test and fix for both HTTP and WebSocket transports: validate against
ResponseItem::id_prefix(), omit foreign reasoning/compaction items with type-invalid IDs from the request copy, and clear type-invalid IDs from portable items without mutating persisted prompt state.The broader handoff design in #38365 remains valuable, but fixing this predicate prevents a deterministic API 400 in existing replay behavior.
The persisted cross-provider response-item ID failure here is a useful parser/diagnostic case for
codex-rescue. The tool does not currently claim to repair arbitrary provider-specific item IDs or encrypted reasoning state; I’m interested in whetherdoctorhandles the affected rollout conservatively without crashing or mutating it.If you still have a synthetic or affected local session available, could you try:
The valuable outcomes are either a bounded diagnostic finding or an explicit unsupported/unknown state—not guessing the intended ID and not replaying it.
Please share only sanitized output plus versions/exit codes. No raw rollouts/SQLite, encrypted provider state, prompts, credentials, or private paths. Repo: https://github.com/shleder/codex-rescue
Thanks. I tested this conservatively on Windows 11 against two affected local rollouts, without sharing or modifying either source file.
Environment:
26.810.7004.00.148.0-alpha.9codex-rescue 0.1.0a3, installed from audited source at commitd68027578ce1862510e57139ffddb09e6d09fe8166 passed, 1 skippedpip index versions codex-rescuedid not find a published distribution in this environment, so I did not use an unverified package sourceSanitized results:
Case A:
Case B:
The second report also classified newer Codex events conservatively:
tool_search_callwithout a name (79),image_generation_call(1),mcp_tool_call_end(13), andweb_search_call(35). It reported one bounded-memory correlation overflow and five outputs without matching calls.So the useful result is:
doctordid not crash, replay provider state, or mutate either rollout. However,0.1.0a3does not currently diagnose the type-specific response-item ID mismatch in this issue (item_...on a reasoning item that requiresrs_...). ItsUNKNOWN_OPERATIONAL_SCHEMA/UNFINISHED_TOOL_CALLfindings also need to be interpreted cautiously for current Codex schemas; some are compatibility gaps rather than proof of rollout corruption.No raw rollout, SQLite content, encrypted state, prompt, credential, thread ID, or private path was included in this test or comment.
Confirming your source-level analysis still holds on today's
main@ 1f41cc5d92 —prepare_response_items_for_requestis unchanged (core/src/client.rs#L943-L949), andResponseItemId::is_prefixedstill accepts anynonempty_nonemptyshape (protocol/src/response_item_id.rs#L36-L39), soitem_…survives exactly as you traced.Two design notes for whoever implements the per-type check, both edge cases the obvious fix can trip on:
rs_,msg_,fc_, …) are an OpenAI Responses API contract, not a wire-format universal — your scenario is custom-provider IDs replayed to OpenAI, but the mirror case (OpenAIrs_IDs replayed to a permissive custom provider) is currently accepted and should stay that way. Scoping the strict validation to providers known to enforce it (or, more simply, validating against the expected-prefix table and clearing on mismatch regardless of provider — a cleared ID is safe everywhere) avoids breaking the lenient direction while fixing yours.encrypted_contentrelies on the item's pairing with its adjacent items; if the strict check starts clearing IDs on validrs_items due to an over-broad table (or a future new prefix), replay quality degrades silently. Worth pairing the fix with a test that a well-formed OpenAI history round-trips with all IDs intact, alongside your foreign-ID rejection tests — the failure mode of an overzealous validator is quieter than the 400 you hit, which makes it easier to ship by accident.Given you've already written the fix and regression tests, this issue looks like a strong candidate for the maintainers to invite a PR on per the contribution policy.
@SimpleZion
Thank you for the careful conservative test on 0.1.0a3 and for the precise gap report — both limitations you identified are addressed in the just-released 0.1.0-alpha.7.
item_...on a reasoning item that requiresrs_...): Alpha7 validates persisted response-item IDs against a per-type prefix table derived from the current upstreamResponseItem::id_prefix()values —reasoning → rs,message → msg,function_call → fc,custom_tool_call → ctc, etc. A prefixed but type-incompatible ID (exactly youritem_-on-reasoning case) is now flagged asINVALID_PERSISTED_ITEM_ID. Legacy unprefixed IDs remain readable upstream and are deliberately not rejected, so this targets the replay-failure class without false-positiving old sessions.UNKNOWN_OPERATIONAL_SCHEMAfalse positives on current schemas:mcp_tool_call_end,tool_search_call,web_search_call, andimage_generation_callare now recognized known operational schema types, and a compatibility filter removes only explicit known-schema false positives from the results. So those 13 ×mcp_tool_call_end, 79 ×tool_search_call, 35 ×web_search_call, and 1 ×image_generation_callentries you saw should no longer be reported as unknown-schema corruption — they're compatibility gaps, as you correctly noted.To re-run on your two affected rollouts (read-only, source untouched):
Expected: the reasoning-item ID mismatch surfaces as
INVALID_PERSISTED_ITEM_ID, and the current-schema event types no longer inflateUNKNOWN_OPERATIONAL_SCHEMA. If you still see a current Codex event type misclassified as unknown, the type name would be very useful so I can extend the table.Your persisted-ID findings are now ported into the Rust adapter: type-aware ID prefixes, known/future operational schema handling, and bounded unfinished call/output correlation.
Migration update: active Codex Rescue development has moved to Vetto: https://github.com/shleder/vetto. The standalone
shleder/codex-rescuerepository remains public as compatibility history. User installation is nownpm install --global @shleddy/vetto@next, with recovery undervetto rescue. The boundary remains read-only/copy-only and does not modify session JSONL or vendor SQLite. The newest diagnostic changes are merged into Vettomainand will be included in a later npm alpha; no source install is requested.