Repository navigation
[Provider compatibility]: zero-output bare SSE error commits a failover combo target #4808
Description
Client or integration
Codex App
Provider or upstream service
Xhub (api15.xhub.chat), through generic openai-responses providers
OpenCodex version
2.56.0
Endpoint or capability
/v1/responses, streaming, strategy: "failover" combos
Current behaviour
A provider can return HTTP 200 with text/event-stream and then emit a bare SSE error before any text, reasoning, tool call, or other meaningful output. A representative redacted frame is:
event: error
data: {"type":"error","error":{"type":"server_error","code":"upstream_server_error","message":"temporarily unavailable"}}
The combo stream preflight does not classify this default bare type: "error" payload as a retryable zero-output terminal. Because an unknown event commits the child conservatively, the combo returns the failed stream instead of trying the next eligible target. In VS Code 1.137.0 / Copilot Chat 0.65.0 this surfaced as The model provider reported a failed response without any error details (event: error).
By contrast, ordinary pre-stream HTTP 503 responses in the same setup do traverse the remaining combo targets.
Expected behaviour
When a bare SSE error arrives before the combo commit boundary and contains concrete retryable provider evidence, such as a structured 429 or 5xx status/code, the combo should classify that attempt through the existing bounded failure policy and may advance to the next eligible target.
Unknown bare errors, request/policy errors, and every error after text, reasoning, tool/action, or other output begins should remain fail-closed and must not be replayed. This matches the replay-safety contract established for zero-output response.failed in #2431/#2449.
Minimal redacted request or reproduction
# Configure combo/repro with two openai-responses targets and strategy: failover.
# Target A returns HTTP 200, Content-Type: text/event-stream, then only:
#
# event: error
# data: {"type":"error","error":{"type":"server_error","code":"upstream_server_error","message":"temporarily unavailable"}}
#
# Target B returns a normal Responses SSE completion.
curl -N http://127.0.0.1:10100/v1/responses \
-H "content-type: application/json" \
-d "{\"model\":\"combo/repro\",\"input\":\"reply with ok\",\"stream\":true}"
# Actual: target A is committed and its error reaches the client; B is not attempted.
# Expected: A is recorded as a retryable zero-output failure and B is attempted.Actual response or error
The model provider reported a failed response without any error details (event: error).OpenCodex service logs do not retain the raw upstream SSE frame. The frame above is a redacted representative of the observed event: error / type: "error" shape; no credentials, affected request bodies, or private provider identifiers are included.
Upstream documentation
No public Xhub specification for this SSE error envelope was found. The compatibility requirement is concrete client behaviour plus the existing OpenCodex combo contract: retryable failures before meaningful output may fail over, while post-output failures must not replay.
Related contract and prior fix: #2431 and #2449.
Suggested mapping or implementation notes
src/server/responses/combo-stream-preflight.ts already supports an explicit caller predicate opting a known bare type: "error" payload into replay, but the default retryableZeroOutputTerminal() accepts only response.failed and the allowlisted transport response.incomplete reasons.
A conservative implementation could:
- recognize only bare error payloads with structured retryable status/code evidence;
- normalize them through
failedTerminalResponse()and the existing combo failure classifier; - preserve the existing
outputCommittedand bounded-buffer barriers; - leave unstructured or ambiguous bare errors committed.
This should be provider-neutral rather than keyed to Xhub.
Additional context and attachments
Environment: macOS 15.2 arm64, OpenCodex 2.56.0, VS Code 1.137.0, Copilot Chat 0.65.0.
Existing related work:
- fix(combos): fail over zero-output stream failures, recording each terminal once #2449: bounded zero-output
response.failedcombo failover (merged) - fix(combo): fail over zero-output transport incompletes (carry of #3227) #3236: zero-output transport
response.incompletefailover (merged) - feat(retry): refetch once on zero-output mid-stream socket reset #3389: zero-output mid-stream socket-reset refetch (draft; distinct transport shape)
No matching open or closed issue was found for default combo handling of bare type: "error" SSE events.
Checks
- I searched existing provider and compatibility issues.
- The request and response were redacted.
- The expected behaviour is based on an upstream specification or a concrete client requirement.
Activity
lidge-jun commentedon Sep 16, 2026
리뷰 · 우선순위 73 / 80
이 이슈는 failover combo가 HTTP 200 +
text/event-stream으로 연 뒤, 출력·reasoning·tool 이전에 bare SSEevent: error(예:type:"error"/server_error/upstream_server_error) 만 받으면 다음 타깃으로 넘어가지 않고 그 자식을 커밋해 버리는 provider-compatibility 구멍입니다. 같은 설정의 pre-stream HTTP 503은 나머지 타깃을 도는데, zero-output bare error만 빠집니다. VS Code/Copilot에서는 상세 없는 failed response로 보입니다.지금
dev(HEADd210c46da) 코드가 원인을 정확히 보여 줍니다.src/server/responses/combo-stream-preflight.ts의 기본retryableZeroOutputTerminal은response.failed와 특정response.incompletereason만 true입니다. baretype === "error"는 false입니다. 동시에comboStreamPayloadCommitsOutput은 알 수 없는 이벤트 타입을 보수적으로 커밋하는데,"error"는 PRE_OUTPUT_CONTROL / TERMINAL 집합에 없어 커밋됩니다. 주석도 “Only an explicit caller predicate may opt a known bare error into replay. Default … commits unknown/error events.” 라고 적어 두었습니다. #2431/#2449 zero-outputresponse.failed재생 안전 계약과 같은 정신으로, 구조화된 5xx/429 증거가 있는 bare error만 커밋 전에 실패로 분류하면 됩니다.최근 tip(#4807 replay refusal, #4813 fence, #4814 refresh classify)과도 잘 맞습니다. “재시도해도 안전한 zero-output 실패”와 “출력이 시작된 뒤의 fail-closed”를 가리는 문제입니다. types/config 분리와 무관합니다.
라인 / 경로 수준의 메모입니다.
src/server/responses/combo-stream-preflight.tsretryableZeroOutputTerminal- bare error를 보지 않음. 여기(또는 호출부 predicate)에 구조화 code/status 기반 5xx·429 허용을 넣는 것이 최소 수정입니다.comboStreamPayloadCommitsOutput-type:"error"기본 커밋. matchedBareError 분기는 retryable일 때만 커밋을 건너뛰므로, predicate가 false면 결국 accepted/commit 경로로 갑니다.정책 경계 - 알 수 없는 bare error·요청/정책 오류·출력 시작 후 오류는 재생 금지(이슈 Expected와 동일). 메시지 substring만으로 열면 #4814가 막으려던 false quarantine과 비슷한 함정이 됩니다. structured code/status를 우선하세요.
재현 - 이슈의 curl combo repro(A: bare error stream, B: 정상 완료)가 충분합니다. 단위 테스트로 preflight에 동일 프레임을 넣으면 CI로 고정하기 쉽습니다.
관련 - #2431/#2449 계약 확장으로 문서화하면 리뷰 비용이 줄어듭니다. 열린 중복으로 보이는 동일 bare-error combo 이슈는 이 목록에 없습니다.
메인테이너의 판단이 필요한 지점
retryableZeroOutputTerminal을 넓힐지, openai-responses/passthrough 호출부에만 predicate를 줄지error.code/ nested status만으로 할지, 메시지 화이트리스트를 허용할지너의 추천
높은 신호로 받아 주세요.
retryableZeroOutputTerminal(또는 명시 predicate)에 zero-output bare error + structured 5xx/429 증거만 true로 두고, 출력 커밋 이후·모호한 error는 fail-closed를 유지하세요. 이슈 repro를 회귀 테스트로 고정. close-don't-rebase 대상 아님. #4807 재생 거절 계약과 충돌하지 않게 “재시도 가능 실패”로만 분류하면 됩니다.이 댓글은 grok-bot이 작성했습니다
lidge-jun commentedon Sep 16, 2026
Tracking: draft PR #4817 addresses this.
Confirmed still present on
dev(2b19983bfd):src/server/responses/combo-stream-preflight.ts:33-42recognizes onlyresponse.failedand selectedresponse.incompleteframes, so an unknown bare SSE error falls through to the commit path at lines 50-54.lidge-jun commentedon Sep 17, 2026
Fixed on
devby #4817 (41f1832f6a), released in 2.58.0.A zero-output bare SSE error is now judged by the existing combo failure classifier. The frames this report showed —
server_errorandupstream_server_error— resolve to 5xx throughhttpStatusFromTerminalErrorand hop to the next target. Unknown bare errors, client errors, and errors after output began stay fail-closed, which is deliberate: those are not safe to replay.