Skip to content

fix(network): bound direct-path response-start timeout and retry on fresh socket (#10214) - #10528

Merged
diegosouzapw merged 1 commit into
diegosouzapw:release/v3.8.50from
excessivechaos:fix/direct-dispatcher-timeout-10214
Aug 20, 2026
Merged

diegosouzapw merged 1 commit into
diegosouzapw:release/v3.8.50from
excessivechaos:fix/direct-dispatcher-timeout-10214

Conversation

@excessivechaos

Copy link
Copy Markdown
Contributor

Fixes #10214 — direct (no-proxy) egress stalls opencode-go / command-code until a service restart.

Root cause

The direct egress path in open-sse/utils/proxyFetch.ts funnels every request through the cached round-robin pool of 32 one-connection Agents (getDefaultDispatcher()). A keep-alive pooled socket that silently drops (half-open, no RST — VPN reconnects, edge idle-drops) surfaces no transport error, so the existing fresh-socket retry (which only fires on UND_ERR/ECONNRESET/fetch failed) never triggers: the request hangs until undici's headersTimeout (600s default) or the caller's per-model deadline, then fails. A service restart drops the stale sockets, which is why the same providers recover immediately — and re-wedge under sustained traffic.

Verified live:

  • model=orchestrator through a long-running gateway: opencode-go leg hangs exactly 120s (combo timeout), zero TCP connections from the gateway process during the hang.
  • Identical request on a fresh isolated instance (same bundle, same DB, same key, same egress): 200 in 1.8s.
  • Same-host direct fetch to the upstream: fast response. All 4 upstream edge IPs respond.
  • Health checks pass throughout (they use a different fetch path with fresh sockets).

Fix

Bound each direct attempt's response-start window (fetch promise = headers only; the body streams through untouched afterwards):

  • New per-attempt bound, default 30s, tunable via OMNIROUTE_DIRECT_HEADERS_TIMEOUT_MS (0 disables — previous behavior).
  • On expiry, retry once on the fresh no-keep-alive dispatcher (getRetryDispatcher) — a brand-new socket that cannot be the zombie — instead of consuming the full 600s headersTimeout/caller deadline.
  • A second timeout (or a non-replayable body) surfaces a classified DIRECT_RESPONSE_START_TIMEOUT error so the combo fails over to the next target; no native-fetch fallback (native fetch pools too and would hang identically).
  • Caller-initiated aborts propagate unchanged (no retry).

Tests

tests/unit/proxyfetch-direct-response-start-timeout-10214.test.ts (3 tests):

  1. Pooled attempt hits a silent zombie socket → bounded timeout → retries on the fresh no-keep-alive dispatcher → success (asserts dispatcher identity getDefaultDispatcher → getRetryDispatcher).
  2. Both attempts stall → classified DIRECT_RESPONSE_START_TIMEOUT surfaces, native fallback never fires.
  3. Healthy fast response → single attempt, untouched by the bound.

Plus all 44 existing proxyFetch/proxyDispatcher tests: 47/47 pass.

End-to-end verification

A/B against a local server that accepts the first POST and never responds (real half-open zombie socket), with OMNIROUTE_DIRECT_HEADERS_TIMEOUT_MS=5000:

Bundle Result
Old (live dist) request hangs 22s+ (no failover — bug reproduced)
This branch (built) 200 in 5418ms — bounded timeout fired, retried on a fresh socket, POST #2 succeeded

Log evidence from the fixed bundle:
[ProxyFetch] Direct response-start timeout (5000ms) on pooled dispatcher — retrying on fresh no-keep-alive dispatcher: 127.0.0.1:19090

Also verified the fixed bundle handles the real opencode-go path: model=orchestrator → 200 in 1.97s streaming deepseek-v4-flash.

excessivechaos added a commit to excessivechaos/OmniRoute that referenced this pull request Aug 16, 2026
excessivechaos added a commit to excessivechaos/OmniRoute that referenced this pull request Aug 16, 2026
@excessivechaos
excessivechaos force-pushed the fix/direct-dispatcher-timeout-10214 branch from 8a90142 to 824eeba Compare August 16, 2026 21:12
@diegosouzapw
diegosouzapw changed the base branch from main to release/v3.8.50 August 17, 2026 05:34
@diegosouzapw

Copy link
Copy Markdown
Owner

Não foi possível revisar o código com segurança: /tmp/review-prs-0816-diffs/10528.diff está vazio (0 bytes). Além disso, o PR aberto aponta para main, enquanto a base solicitada é release/v3.8.50. A metadata disponível descreve 584.103 adições, 105.697 remoções e 100 arquivos, portanto não é seguro inferir o patch pretendido a partir dela. Solicito um diff não vazio contra release/v3.8.50 e o retarget do PR antes de avaliar feature, segurança, testes, impacto cross-layer ou a regra #18. O alerta base-red #9985 permanece herdado e não foi atribuído ao PR.

@diegosouzapw

Copy link
Copy Markdown
Owner

The PR is now correctly targeted at release/v3.8.50, but the original review artifact had an empty diff and could not establish the intended change safely. Please provide a current-head diff/reproduction and rerun the release gates before it is reconsidered. The PR remains open.

@diegosouzapw diegosouzapw added deferred-v3.8.50 Adiada para o ciclo v3.8.50 (validacao VPS, refactor, ou escopo grande) merge-train-deferred PR ejetada do merge-train — triagem separada labels Aug 17, 2026
@excessivechaos
excessivechaos force-pushed the fix/direct-dispatcher-timeout-10214 branch 2 times, most recently from 8e2b729 to af9b9f2 Compare August 17, 2026 15:00
@excessivechaos
excessivechaos force-pushed the fix/direct-dispatcher-timeout-10214 branch from af9b9f2 to 142ae93 Compare August 17, 2026 15:27
@diegosouzapw diegosouzapw removed merge-train-deferred PR ejetada do merge-train — triagem separada deferred-v3.8.50 Adiada para o ciclo v3.8.50 (validacao VPS, refactor, ou escopo grande) labels Aug 17, 2026
@diegosouzapw
diegosouzapw merged commit ce6249c into diegosouzapw:release/v3.8.50 Aug 20, 2026
16 checks passed
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…dispatcher-timeout-10214

fix(network): bound direct-path response-start timeout and retry on fresh socket (diegosouzapw#10214)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(network): direct dispatcher stalls opencode-go and command-code until service restart

2 participants