You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Bug] Stream gate falsely rejects valid Responses API compaction/tool-call streams as empty_stream; stream_gate_mode=off not honored when replay enabled #1410
CCH's stream content gate (and the stream_gate_precommit path) falsely rejects valid OpenAI Responses API streams as empty_stream when the upstream response contains no response.output_text.delta frames. Observed for two cases:
Compaction streams — gpt-5.6-sol returns a context-compaction response whose terminal response.completed frame has output:[{type:"compaction", encrypted_content}] and no text content.
Tool-call streams — the model returns response.custom_tool_call_input.delta frames (tool-call argument streaming) with no text deltas.
In both cases the upstream returns HTTP 200 with a complete, well-formed SSE stream, but the gate classifies it as empty_stream and returns 502, which burns the provider's circuit breaker.
Reproduction
CCH → codex-type provider → POST /v1/responses with stream:true. Upstream (an OAuth aggregator forwarding to OpenAI Codex gpt-5.6-sol) returns 200 with a standard Responses API SSE sequence. When the model emits a compaction or a tool call, the stream contains reasoning/compaction/tool-call frames and zeroresponse.output_text.delta frames.
The gate's content detector only recognizes response.output_text.delta (text deltas) as "valid content". When a stream contains only compaction and/or tool-call frames, the detector reaches a terminal frame without ever seeing "valid content" → classifies as empty_stream.
Additionally, the precommit gate is not controlled by stream_gate_mode. From the compiled source, the rejection path fires when:
So even with stream_gate_mode = off (or shadow), the precommit gate still rejects for any request whose replayState.role === "owner" — i.e. virtually every primary request while request Replay is enabled. This matches the admin UI description: "Replay owner … regardless of mode, always retains the pre-content safety check."
#1404 ("fix(proxy): support remote compaction v2 passthrough") was meant to fix #1398 (compaction streams rejected as empty_stream). We are running a build that contains #1404 (compaction passthrough code present), yet compaction streams are still rejected by the precommit gate. It looks like #1404 handled the compaction wire path but did not teach the gate's content detector to recognize compaction/tool-call frames as valid content.
Workaround (verified)
Disable request Replay (replay_enabled = false) so requests no longer carry replayState.role === "owner".
Set stream_gate_mode = off.
With both, the precommit gate no longer force-runs and compaction/tool-call streams pass through. Verified: empty_stream rejections drop to 0 and the provider circuit stops burning. (Cost: loses Replay's concurrent-request dedup and reconnect-tail. Does not affect prompt cache — that's orthogonal, via prompt_cache_key.)
Impact
Any codex / Responses-API provider whose upstream performs context compaction or tool calls will be intermittently rejected, burning its circuit breaker and eventually returning 503 "no available providers" to the client. For tool-heavy clients (Claude Code / Codex CLI) this is near-constant — in our case the provider accrued 60+ empty_stream failures and the codex path was effectively unusable until the workaround above.
Reproduced with stream_gate_mode = enforce (default); switching to off/shadow does not help while Replay is enabled
Upstream: OAuth aggregator forwarding to OpenAI Codex gpt-5.6-sol
Ask
Could the gate's "valid content" detection be extended to treat Responses-API compaction output (type: compaction) and tool-call streams (response.custom_tool_call_input.delta) as valid content?
And/or should the precommit gate honor stream_gate_mode = off even for replay owners (or expose a separate toggle)?
Happy to provide fuller SSE captures if useful. Thanks!
Summary
CCH's stream content gate (and the
stream_gate_precommitpath) falsely rejects valid OpenAI Responses API streams asempty_streamwhen the upstream response contains noresponse.output_text.deltaframes. Observed for two cases:gpt-5.6-solreturns a context-compaction response whose terminalresponse.completedframe hasoutput:[{type:"compaction", encrypted_content}]and no text content.response.custom_tool_call_input.deltaframes (tool-call argument streaming) with no text deltas.In both cases the upstream returns HTTP 200 with a complete, well-formed SSE stream, but the gate classifies it as
empty_streamand returns 502, which burns the provider's circuit breaker.Reproduction
CCH →
codex-type provider →POST /v1/responseswithstream:true. Upstream (an OAuth aggregator forwarding to OpenAI Codexgpt-5.6-sol) returns 200 with a standard Responses API SSE sequence. When the model emits a compaction or a tool call, the stream contains reasoning/compaction/tool-call frames and zeroresponse.output_text.deltaframes.Observed error (compaction case):
{ "error": { "type": "stream_gate_precommit", "reason": "empty_stream", "family": "openai-responses", "frames_seen": 6, "buffered_bytes": 141511, "echo_excluded_bytes": 73346, "frame_preview": "{\"type\":\"response.completed\",\"response\":{\"id\":\"resp_...\",\"object\":\"response\",\"status\":\"completed\",\"model\":\"gpt-5.6-sol\",\"output\":[{\"id\":\"cmp_...\",\"type\":\"compaction\",\"encrypted_content\":\"...\"}]}}" } }For the tool-call case, a replay-payload capture of a rejected stream showed:
→ same
empty_streamrejection.Root cause
The gate's content detector only recognizes
response.output_text.delta(text deltas) as "valid content". When a stream contains only compaction and/or tool-call frames, the detector reaches a terminal frame without ever seeing "valid content" → classifies asempty_stream.Additionally, the precommit gate is not controlled by
stream_gate_mode. From the compiled source, the rejection path fires when:So even with
stream_gate_mode = off(orshadow), the precommit gate still rejects for any request whosereplayState.role === "owner"— i.e. virtually every primary request while request Replay is enabled. This matches the admin UI description: "Replay owner … regardless of mode, always retains the pre-content safety check."Why this isn't covered by #1404
#1404 ("fix(proxy): support remote compaction v2 passthrough") was meant to fix #1398 (compaction streams rejected as empty_stream). We are running a build that contains #1404 (compaction passthrough code present), yet compaction streams are still rejected by the precommit gate. It looks like #1404 handled the compaction wire path but did not teach the gate's content detector to recognize compaction/tool-call frames as valid content.
Workaround (verified)
replay_enabled = false) so requests no longer carryreplayState.role === "owner".stream_gate_mode = off.With both, the precommit gate no longer force-runs and compaction/tool-call streams pass through. Verified:
empty_streamrejections drop to 0 and the provider circuit stops burning. (Cost: loses Replay's concurrent-request dedup and reconnect-tail. Does not affect prompt cache — that's orthogonal, viaprompt_cache_key.)Impact
Any codex / Responses-API provider whose upstream performs context compaction or tool calls will be intermittently rejected, burning its circuit breaker and eventually returning 503 "no available providers" to the client. For tool-heavy clients (Claude Code / Codex CLI) this is near-constant — in our case the provider accrued 60+
empty_streamfailures and the codex path was effectively unusable until the workaround above.Environment
codex, endpoint/v1/responsesstream_gate_mode = enforce(default); switching tooff/shadowdoes not help while Replay is enabledgpt-5.6-solAsk
type: compaction) and tool-call streams (response.custom_tool_call_input.delta) as valid content?stream_gate_mode = offeven for replay owners (or expose a separate toggle)?Happy to provide fuller SSE captures if useful. Thanks!