Repository navigation
Enhancement: transparently refetch once on zero-output mid-stream socket reset (generic passthrough lanes) #3384
Description
Client or integration
ZCode / dsh / Codex CLI / Codex App (any client of the Responses native passthrough or native chat SSE lanes)
Area
Proxy and routing — upstream body relay (mid-stream transport failure)
Summary
A mid-stream socket reset after HTTP 200 + SSE headers, but before any body byte is relayed to the client, currently kills the turn with a terminal response.failed / upstream_reset (retryable: false). The client (ZCode/dsh/Codex) has no pi-style turn-level retry, so the turn stops with Turn execution failed ... The socket connection was closed unexpectedly.
This is a real, recurring shape on this setup: opencodex → Clash proxy → opencode.ai/zen (Cloudflare terminates idle keep-alive connections server-side; Bun's fetch pool reuses the half-closed socket), with long reasoning streams (muse-spark) making the mid-stream window large.
Root cause
fetchWithTransientRetry / fetchWithResetRetry (src/lib/upstream-retry.ts) only cover pre-stream failures — fetch() rejecting before response headers. Once headers arrive and the relay starts reading the body, a reset surfaces as a ReadableStream.read() rejection, which is outside every pre-stream retry wrapper. The eager relay (relay-eager.ts) and the tee/pull relay (relay.ts) catch it and synthesize response.failed / upstream_reset — deliberately NOT a resend (relay.ts:251-257 comment), because replaying after partial output would duplicate tool-call side effects.
Adjacent work (not equivalent)
- Kiro mid-stream socket close (heartbeat-only, 0 output tokens) is hardcoded retryable:false → terminal 502 #519 fixed this for the Kiro adapter only: heartbeat-only / zero-output mid-stream close is now
retryable: trueso the client can retry. The generic Responses passthrough and chat SSE lanes have no equivalent gate. - [Bug]: failover combos stop on zero-output SSE terminal failures and model-EOL HTTP 410 responses #2431 / fix: fail over zero-output incomplete combo streams #3227 / fix(combo): fail over zero-output transport incompletes (carry of #3227) #3236 / fix(combos): fail over zero-output stream failures, recording each terminal once #2449 handle zero-output stream failures for combo failover only. A single-provider passthrough still fails closed.
- [Enhancement]: Mid-stream OpenAI socket resets can break Codex account affinity #919 tracks account-affinity consequences of mid-stream resets, not recovery.
Suggested fix (mirrors pi, more conservative)
Wrap the passthrough body before tee/eager relay with a zero-output gate:
- Only when zero bytes were consumed and no protocol terminal was seen (nothing relayed to the client), and the error matches
isConnectionResetError(socket connection was closed unexpectedly/ECONNRESET/EPIPE), and the client signal is alive: - Refetch once with
connection-resetrecovery init (Connection: close+keepalive: false) so Bun never reuses the half-closed pooled socket, and continue relaying the fresh body. - Any partial-output failure keeps the existing fail-closed tail (replaying after emitted tool calls would duplicate side effects).
I have a working local implementation of exactly this (a wrapWithZeroOutputRefetch body wrapper + refetchOnZeroOutputReset helper in src/lib/upstream-retry.ts, wired into both the Responses passthrough and the native chat SSE lane, verified byte-identical re-application after upgrades). Happy to turn it into a PR following the repo's conventions if maintainers are open to it.
Environment
- opencodex 2.40.0 / 2.41.0, Bun 1.4.0, Windows
- Provider: opencode-zen/muse-spark-1.3-contributor-free via local gateway → proxy 127.0.0.1:7897 → opencode.ai/zen
Activity
github-actions commentedon Sep 3, 2026
Issue closed: use an issue template
Thanks for taking the time to submit this issue. It was closed automatically because it was not opened with a recognized issue template (Bug report, Feature request, Documentation, or Provider compatibility). Freeform and API-opened issues cannot skip this check.
Please edit the issue to add:
See the Contributing guide for details.
Once the report passes the automated checks, it will be reopened automatically unless a maintainer has changed its state.