Repository navigation
[Bug]: Responses-compatible gateways can return sparse lifecycle snapshots that Codex clients do not commit #893
Description
Client or integration
Codex App and Codex CLI
Area
Streaming
Summary
Some OpenAI Responses-compatible gateways return a successful SSE stream but omit canonical fields from lifecycle snapshots. OpenCodex currently relays those snapshots unchanged, and current Codex clients may reject or fail to render the turn even though the upstream request completed with HTTP 200.
The affected payloads are structurally sparse rather than malformed JSON. Examples include lifecycle response objects without output, parallel_tool_calls, tool_choice, or tools; message items without role or content; output_text parts without annotations; reasoning items without summary; and text delta/done events without logprobs.
Expected: an explicit provider-local compatibility option can backfill only missing or structurally invalid canonical fields on the client-facing SSE branch. Existing upstream values must remain authoritative, and the default passthrough path must remain unchanged.
Reproduction
- Configure a custom provider with the
openai-responsesadapter and a Responses-compatible endpoint. - Have the upstream return a successful stream containing sparse lifecycle events equivalent to:
event: response.created
data: {"type":"response.created","response":{"id":"resp_example","object":"response"}}
event: response.output_item.added
data: {"type":"response.output_item.added","item":{"id":"msg_example","type":"message"}}
event: response.output_text.delta
data: {"type":"response.output_text.delta","delta":"hello"}
event: response.completed
data: {"type":"response.completed","response":{"id":"resp_example","object":"response"}}
- Send a short request through
/v1/responsesfrom Codex. - Observe that the upstream and proxy complete with HTTP 200, while the Codex client may show no final assistant message or fail to commit the response lifecycle.
- Compare with the same semantic events after the missing canonical fields are backfilled; the client completes normally.
The sample above is a synthetic, redacted equivalent of the observed wire shape and contains no provider-specific data.
Version
2.10.0 (f9b9440c)
Operating system
macOS 26.5.2 (arm64)
Provider and model
Custom OpenAI Responses-compatible gateway / GPT-compatible routed model
Logs or error output
Upstream HTTP status: 200
Proxy terminal status: completed
Codex client result: no committed final assistant message for the sparse snapshot streamScreenshots and supporting files
None. A synthetic SSE fixture and regression tests are included in the accompanying PR.
Redacted configuration
{
"providers": {
"example": {
"adapter": "openai-responses",
"baseUrl": "https://gateway.example/v1",
"responsesPath": "/responses"
}
}
}Suggested fix
Add a disabled-by-default provider option such as responsesSnapshotRepair. When enabled, apply a client-facing SSE payload rewrite that:
- backfills only missing or structurally invalid canonical fields;
- preserves every valid value supplied by the upstream;
- covers response lifecycle snapshots, output items, content parts, reasoning summary parts, and output-text logprobs;
- composes with existing image-call and item-ID rewrites;
- keeps inspection and persistence on the raw upstream bytes;
- can run inline on the existing explicit Darwin eager-relay path without reintroducing the tee/JS-pull retention shape.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
github-actions commentedon Aug 2, 2026
Automated translation bookkeeping — detected language: unknown.
lidge-jun commentedon Aug 3, 2026
Confirmed still real on current
dev, and PR #928 is now open against it.For the record on what happened in between: PR #894 addressed this and was closed unmerged. None of its four commits is an ancestor of
dev, and the #892 stack did not absorb the feature — I checked rather than assumed, because "a PR existed" and "the fix shipped" are easy to conflate.src/server/responses/core.ts:1819-1827still composes only image-generation and item-ID rewrites;responses-snapshot-repair.ts, its config flag, and its tests do not exist.Why #894 did not land: 1,168 additions across 23 files, carrying a rewrite of the Darwin relay design unit alongside the fix. The breadth was the problem, not the idea.
The shape worth landing, recorded in
devlog/_plan/260803_sparse_snapshot_repair/000_plan.md: provider-local and default-off, preserving upstream values rather than overwriting them, bounded reconstruction with the translator budget charged and released, covering both SSE and JSON, and leaving the relay design unit alone.Reviewing #928 against that.
Merge pull request #1025 from lidge-jun/codex/stack-03-sparse-snapsho…
feat(responses): provider opt-in sparse lifecycle snapshot repair (li…
fix(responses): fold review blockers into the snapshot repair (lidge-…
fix(responses): round 2 fold — disposal chain, identity correlation, …
fix(responses): round 3 fold — text-replacement bounds, tainted reten…
lidge-jun commentedon Aug 5, 2026
Fixed on
devin #1025 (merged as064686b41).The repair is opt-in per provider. A sparse snapshot gets its missing fields backfilled, and — the part that actually resolves the reported symptom — the missing lifecycle events are synthesized, so the Codex-facing consumer commits the message instead of waiting on a stream that never announces it. Field normalization alone was not enough: the client needs the events, not a repaired final snapshot.
An ambiguous or contradictory terminal fails closed to a canonical empty output rather than inventing content, and an explicit
output: []is treated as authoritative.Thanks to @0xWinner98 (#928) — the config shape, field backfills, JSON path, and bounds all come from that PR.
Not in a release yet. This is on
dev, andmainis atv2.10.0. It ships in the next release.docs(devlog): archive eleven closed units, and say why thirty-one stay
feat(responses): provider opt-in sparse lifecycle snapshot repair (li…
fix(responses): fold review blockers into the snapshot repair (lidge-…
fix(responses): round 2 fold — disposal chain, identity correlation, …
fix(responses): round 3 fold — text-replacement bounds, tainted reten…
Merge pull request lidge-jun#1025 from lidge-jun/codex/stack-03-spars…
docs(devlog): archive eleven closed units, and say why thirty-one stay