Repository navigation
Description
Client or integration
Codex App
Area
Proxy and routing
Summary
Also reproduced with a direct HTTP request to the local /v1/responses endpoint.
On OpenCodex 2.88.0, an existing conversation can stop working after switching models or providers. Its history still contains reasoning encrypted_content that the destination backend cannot verify, but some rejection shapes do not enter the existing one-shot recovery path.
The important failure mode is HTTP 200 with a failed SSE terminal and no assistant answer. Checking the HTTP status alone incorrectly suggests success. Further client retries can replay the same rejected history.
The expected behavior is to continue the original conversation while preserving its complete on-disk history. Recovery should operate on an outbound request copy, require an explicit upstream ciphertext rejection, and remain limited to one sanitized rebuild before any output or tool call has committed. Valid native ciphertext and normal same-model continuations must remain supported.
Reproduction
Observed workflow
- Configure native OpenAI/ChatGPT Responses and DeepSeek Responses routes.
- Continue a conversation long enough to accumulate reasoning history. Tool calls and results may also be present.
- Switch the model/provider within that conversation and continue. Restoring an existing conversation after restarting OpenCodex is another relevant case because the in-memory route record may be unavailable.
- If the destination rejects the replayed ciphertext, inspect the SSE terminal and actual answer, not only the HTTP status.
Minimal synthetic probe
Save the following as issue-repro.json. The deliberately invalid value exercises rejection recovery; it is not real ciphertext or private conversation data and does not imply that arbitrary encrypted history should be removed.
{
"model": "gpt-6.1-sol",
"stream": true,
"store": false,
"instructions": "Answer the final user question concisely. Do not call tools.",
"reasoning": { "effort": "low" },
"tools": [],
"input": [
{
"type": "message",
"role": "user",
"content": [{ "type": "input_text", "text": "For this compatibility test remember code MAPLE-73. Reply only with that code." }]
},
{
"type": "reasoning",
"summary": [],
"encrypted_content": "c1f30500-0000-0000-0000-000000000000-0"
},
{
"type": "message",
"role": "assistant",
"content": [{ "type": "output_text", "text": "MAPLE-73" }]
},
{
"type": "message",
"role": "user",
"content": [{ "type": "input_text", "text": "What code did I ask you to remember? Reply only with the code." }]
}
]
}On Windows:
curl.exe --no-buffer http://127.0.0.1:10100/v1/responses `
-H "Content-Type: application/json" `
-H "Accept: text/event-stream" `
--data-binary "@issue-repro.json"This requires an available native OpenAI/ChatGPT route. Supply the instance's authentication header separately if local data-plane authentication is enabled.
Before the local patch: HTTP 200, failed stream terminal, no remembered code returned.
With the local prototype: completed terminal, correct MAPLE-73 answer, and no client-visible ciphertext error. The usage record showed two sends, with the second attributed to opaque-blob-rejection recovery.
Version
@bitkyc08/opencodex 2.88.0 (global npm; bundled Bun 1.4.2). Earlier/later releases not systematically tested; no fixed upstream release claimed.
Operating system
Windows 11, 64-bit, OS version 10.0.26200.
Provider and model
OpenAI/ChatGPT native Responses: gpt-6.1-sol; DeepSeek Responses: deepseek/deepseek-flash. Tested OpenAI → DeepSeek → OpenAI, including tool-call history.
Logs or error output
The encrypted content <redacted> could not be verified. Reason: Encrypted content could not be decrypted or parsed.
Error type: invalid_request_error
Error code: invalid_encrypted_content
The rejection can also use the exact verification message without a dedicated code, or arrive as an `error` / `response.failed` event inside HTTP 200 SSE. An intermediate projection may surface it as `502 upstream_server_error`. Ordinary network failures, rate limits, and quota errors should not qualify for ciphertext recovery.Screenshots and supporting files
Root-cause analysis
In the examined 2.88.0 code:
src/server/responses/core-opaque-recovery.ts: the 502 exception inshouldAttemptOpaqueBlobRecovery()requires encrypted function output and its specific decrypt/decode rejection. A reasoning-only ciphertext rejection does not satisfy that exception.src/server/responses/passthrough-dispatch.ts: stream preflight is gated mainly on encrypted function output or_nativeReasoningRetainedAcrossRoute. Restored history can still carry rejected reasoning state without that retention marker, particularly when route provenance is missing after a restart.- The recovery boundary also needs to avoid mutating caller-owned history. Proxy-owned
ocxr1:envelopes must be distinguished from native ciphertext: their readable content can be decoded, whereas native ciphertext cannot be reconstructed by the proxy.
Proposed solution
- Recognize explicit rejection identities consistently. Share the classifier across HTTP errors and supported SSE error envelopes, including nested and flat forms. Admit a 502 only when the response explicitly identifies a ciphertext rejection and the actual outbound body still contains opaque state. Generic gateway errors must remain excluded.
- Cover restored/cold history. On Responses-compatible routes, inspect the bounded, zero-output stream prelude when the outbound request contains opaque history, even if no retention marker or route-cache entry exists. Preserve the stream unchanged if it succeeds or commits output.
- Rebuild an outbound copy once. Remove rejected, nonportable reasoning/compaction state through the existing serializer and drop foreign reasoning item IDs where required. Preserve message order, tool arguments, results, and
call_idassociations. Do not rewrite the on-disk conversation. If a body is cloned, carry over its existing privacy/non-persistence policy. - Preserve decodable content. Decode recognized proxy-owned envelopes and retain their real text. In local testing, unsigned reasoning summaries alone were not sufficient for the destination model to use that text; projecting it into a clearly labelled historical message worked. Never fabricate plaintext for native ciphertext or describe it as decrypted.
- Keep retry boundaries strict. Respect the shared send budget and one-shot guard. Do not replay after text, a tool-call event, or other committed output. A repeated rejection remains terminal. Keep valid native ciphertext on unaffected continuations, and retain the original history for switching back.
Temporary local workaround and deployment
A version-specific outbound compatibility patch was applied to the installed 2.88.0 package and the actual Bun service was restarted. The four affected modules were:
src/server/responses/core-opaque-recovery.ts
src/server/responses/passthrough-dispatch.ts
src/server/responses/combo-stream-preflight.ts
src/adapters/openai-responses/reasoning.ts
For an npm installation, the patch must reach the source actually loaded by the service. Verify that the running instance has reloaded it, then check a completed SSE terminal and the expected answer; a successful restart message or health endpoint alone is insufficient. npm upgrades/reinstalls can overwrite an ad-hoc source patch.
No configuration-only workaround has been verified. Creating a new conversation, deleting history, or clearing context is not the proposed solution.
Local validation and limits
- The installed-version prototype passed 39 targeted tests and 11 online cases covering rejection recovery, cross-provider round trips, tool history, valid native reasoning replay, and readable proxy reasoning/compaction envelopes.
- Valid native same-model replay required one send, without ciphertext recovery.
- The complete file hashes of two affected conversations remained unchanged.
- The original conversation was not retried in full in the Codex UI. Only a read-only excerpt of its real history was replayed online; this does not establish full recovery of the original conversation.
- Other providers and operating systems remain unverified. These passing counts belong to the local 2.88.0 prototype, not the full upstream suite. A separate broader development run still had unresolved failures; no upstream-ready patch or PR is included with this report.
Related closed reports:
- reasoning encrypted_content was not issued to this caller
- Azure OpenAI does not recover invalid_encrypted_content after a provider switch
This report focuses on the remaining 502 / HTTP 200 SSE recovery gap when reasoning history is present but the retention marker or route record is unavailable.
Redacted configuration
{
"providers": {
"openai": {
"adapter": "openai-responses",
"baseUrl": "https://chatgpt.com/backend-api/codex"
},
"deepseek": {
"adapter": "openai-responses",
"baseUrl": "https://api.deepseek.com"
}
}
}Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
github-actions commentedon Oct 19, 2026
Automated translation bookkeeping — detected language: English.
Ingwannu commentedon Oct 19, 2026
Confirmed the current cold-history/pre-output gap with regression fixtures and opened Draft #7383. It reuses actual outbound opaque-state detection, bounded stream preflight, strict nested/flat rejection identities and the existing one-shot outbound-only rebuild. Flat errors retain their validated identity through projection; generic 502, contradictory envelopes, committed text/tools and repeated rejection retain their guards.
Final capped Linux/Bun 1.4.3 run: 165 passed / 0 failed across six focused opaque-state, Azure, retention and combo-preflight files. Cold native success keeps its blob, store:false/tool-pair controls pass, and the input is preserved. Existing generic HTTP-502 retries remain separately counted (three generic sends before the one sanitized rebuild in that fixture). No native ciphertext decryption, new plaintext reconstruction rule, on-disk rewrite, real provider call or complete original Codex UI conversation replay was performed. Current-head CI and independent replay-boundary review remain pending; no landed/released fix is claimed.
lidge-jun commentedon Oct 19, 2026
Fixed on dev by #7388 (squash c120eea): cold-history opaque-state recovery now runs when the outbound body carries opaque state, without needing the process-local retention marker. It ships in the next release (2.90.0).