[Bug] OpenCodex 2.88.0: existing threads fail after model/provider switching when encrypted reasoning is rejected inside HTTP 200 SSE
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- typescript
Research direction
Start with src/server/responses/core-opaque-recovery.ts and src/server/responses/passthrough-dispatch.ts, then trace the related stream preflight and reasoning serializer in src/server/responses/combo-stream-preflight.ts and src/adapters/openai-responses/reasoning.ts. Compare HTTP error and HTTP 200 SSE rejection handling, using the synthetic probe in the issue to reproduce the failure. Done means one bounded recovery handles explicit ciphertext rejections while preserving valid native history, tool associations, and on-disk conversation data; the report says no upstream-ready patch or PR is included.
Written by the indexing model from the issue text.
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.
- Dominant language
- TypeScript
- Stars
- 16.9k
- Forks
- 1.3k
- Avg merge
- 7h 34m
- Merged PRs (30d)
- 667
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from lidge-jun/opencodex
-
Transient OAuth login failure persists in getLoginStatus and masks active account in dashboardPossibly taken A pull request linked to this issue is open or already merged. Openaccount-pool bug gui
Difficulty 2/5 1-3 hours Newbie friendliness 67/100
lidge-jun/opencodex#7378 · 2 comments ·
Maintainers usually reply within 1 day
-
account-pool enhancement needs-design proxy
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
lidge-jun/opencodex#6737 · 1 comment ·
Maintainers usually reply within 1 day
-
fix(google-antigravity): discoverAntigravityProject fails with missing_project when project is not auto-discovered, rejecting valid tokensPossibly taken @yobushka claimed this today. Openaccount-pool bug
Difficulty 3/5 1-2 days Newbie friendliness 35/100
lidge-jun/opencodex#7382 · 2 comments ·
Maintainers usually reply within 1 day
-
account-pool bug needs-design provider proxy
Difficulty 4/5 3-5 days Newbie friendliness 42/100
lidge-jun/opencodex#7380 · 3 comments ·
Maintainers usually reply within 1 day
-
[Bug] 2.88.0: opted-in pool account leaves rotation 5 min after its last credit read once its 5h window is full; requests fall to the hard-locked main accountPossibly taken A pull request linked to this issue is open or already merged. Openaccount-pool bug
Difficulty 4/5 3-5 days Newbie friendliness 48/100
lidge-jun/opencodex#7377 · 2 comments ·
Maintainers usually reply within 1 day
All issues in lidge-jun/opencodex
Similar issues
-
docs(api): openapi.yaml still documents gif-tools optimize colors as 2-256 instead of 2, 4, 16, 256Openarea: backend documentation priority: low
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
snapotter-hq/SnapOtter#2919 ·
Maintainers usually reply within 1 day
-
accepted :+1: bug db/redis
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
beekeeper-studio/beekeeper-studio#4882 · 1 comment ·
Maintainers usually reply within 1 day
-
ready-for-triage
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
konflux-ci/konflux-ui#1630 · 1 comment ·
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 67/100
ahmedkhaleel2004/gitdiagram#233 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
simkimsia/netlify-axi#19 ·
Maintainers usually reply within 1 day