Repository navigation
Description
Description
Subagents running on a ChatGPT subscription (providerID openai, codex auth, model
gpt-5.6-sol) freeze mid-run and never recover. The parent session stays busy forever,
queued prompts are never delivered, and the only escape is interrupting the whole
session, which kills all subagents.
I caught this live on two TUI instances and diagnosed it. Both processes were blocked
on ESTABLISHED connections to chatgpt.com's edge that had been dead-idle for ~50
minutes. The flow was silently dropped along the path (CDN/NAT idle timeout, no
FIN/RST ever delivered):
ESTAB 0 0 10.x.x.x:53326 104.18.32.47:443 lastsnd:2907584 lastrcv:2893284 (no keepalive timer)
opencode.log stops at the request start and never logs another line for that session:
23:54:03 message=stream providerID=openai modelID=gpt-5.6-sol ... agent=security-auditor mode=subagent
Why it hangs forever (v1.18.3, code identical on dev):
- wrapSSE in packages/opencode/src/provider/provider.ts is only armed when the
chunkTimeout provider option is set. There is no default for any provider. - Bun's own fetch timeout is explicitly disabled:
timeout: false(provider.ts:1758,
ref Respect timeout passed to fetch oven-sh/bun#16682). - The openai headerTimeout default (10s, provider.ts:35,208) only bounds
time-to-headers. After headers, reader.read() blocks with nothing above it. - Both retry layers (ai-sdk maxRetries and SessionRetry.policy in processor.ts) are
error-driven. A silent stall throws nothing, so they never fire.
Verified workaround: setting provider.openai.options.chunkTimeout (e.g. 120000) in
opencode.json. When it fires, MessageV2.fromError maps the ResponseStreamError to a
retryable APIError, the turn auto-retries and the subagent resumes with full context.
So a small fix seems possible: add a default chunkTimeout for openai next to
OPENAI_HEADER_TIMEOUT_DEFAULT, and/or enable TCP keepalive on provider sockets.
Also verified: for an already-hung process, killing just the dead socket with
sudo ss -K dst <edge-ip> dport = 443 unsticks it without restarting opencode and
the subagent auto-resumes — the app recovers fine once the socket errors, it just
never notices a silent stall on its own.
Related: #11865, #33028, #29129.
Plugins
@slkiser/opencode-quota
OpenCode version
1.18.3
Steps to reproduce
- Start a task-tool subagent on the openai/ChatGPT subscription path with a
long-reasoning model. Wait formessage=stream providerID=openaiin the log. - Silently blackhole that connection mid-stream to simulate a CDN/NAT idle drop
(no RST, packets just vanish):
sudo iptables -A INPUT -p tcp --sport 443 -s -j DROP
sudo iptables -A OUTPUT -p tcp --dport 443 -d -j DROP - Subagent spinner runs forever, prompts queue and are never delivered, no timeout
or retry ever fires.ss -tinoeshows the socket idle with no timer armed. - Repeat with provider.openai.options.chunkTimeout set: the same scenario aborts
after the timeout and auto-retries successfully.
Screenshot and/or share link
No response
Operating System
Debian 13, Linux 6.12.90+deb13.1-amd64
Terminal
tmux over SSH
Activity
github-actions commentedon Jul 18, 2026
This issue might be a duplicate of existing issues. Please check:
Your issue adds valuable specifics (the
chunkTimeoutcode path analysis,wrapSSEonly armed when the option is explicitly set, and thess -Kworkaround), which may help move those issues forward.VINODvoid commentedon Jul 21, 2026
Dug into this on dev — the diagnosis holds up. In
packages/opencode/src/provider/provider.ts, wrapSSE only kicks in if a positive chunkTimeout is passed (L38), otherwise it hands the SSE body straight back unwrapped. The openai provider sets a headerTimeout (L208) but never a chunkTimeout, and the header one only covers time-to-headers. So once headers land, a dead stream just sits on reader.read() forever, nothing throws, and none of the retry layers ever get a chance to fire. Lines up with what you saw.Good news is the recovery already works when chunkTimeout is set — wrapSSE throws ResponseStreamError, it gets mapped to a retryable error, and the turn retries on its own. So it's genuinely just a missing default.
Fix is basically adding one next to the header default:
Two things I'd rather not guess at, since this hits every OpenAI user. First, the value — it triggers on silence between chunks, and reasoning models can go quiet for a while mid-thought, so too low and you'd kill real streams. Your 120s seems safe; I'd maybe go 180s to be safe. Second, scope — meta, xai, github-copilot and azure all stream through the same path with no timeout either, so should this default cover just openai or all of them?
Once that's decided I'm happy to open a PR with a test for it.
niStee commentedon Aug 24, 2026
Additional data point: same silent-stall signature on the litellm provider path (model
minimax-m3), opencode 1.18.18, Linux — so this is not specific to the openai/ChatGPT transport.Evidence comes from a read-only investigation of the local session DB (
~/.local/share/opencode/opencode.db, queried via SQLite; evidence-snapshot SHA-256e31c2347304401f99fdc2332b3f903c42b708d7b7a629ed3847e80b27bed9c08) plus scoped runtime-log records. The active process was never interrupted.Affected main session
ses_fd8d98c6cffePtOPVMJZv51ocX(agentbuild):stream providerID=litellm modelID=minimax-m3log records with long gaps between them — dispatch happens, then silence, matching the "log stops and never logs another line" pattern described above.finish; the latest unfinished message's tool part (prt_033dfb886001C0mXLSKDy4cp5q) remained persisted asrunningat inspection time.Cohort context (all local
minimax-m3sessions; pre-registered descriptive thresholds, 60 s cutoff):This supports the provider-neutral idle watchdog in #40010 covering non-openai providers by default. I will set
provider.litellm.options.chunkTimeoutas a workaround and can report back whether the incident rate drops.(Local paths and machine details sanitized; stable IDs retained so I can run follow-up queries if maintainers need them.)
moaines commentedon Sep 7, 2026
Independent corpus confirming this for pre-default binaries, plus one hang surviving the default — opencode v1.18.29, Linux, openai-compatible gateway (z-ai/glm-5.3-flash), code dev@57ef382843.
Corrected mining (aborts excluded): 341 assistant messages with empty finish → ~308 ordinary aborts (MessageAbortedError + time.completed set) to exclude + 11 fast aborts with completed present to distinguish → 22-33 true hangs by strictness (finish NULL + error NULL, never completed). In-flight message excluded — same DB signature as a hang.
31/33 sampled hangs (94%) are on binaries 1.1.49→1.18.27 without default (before 04 Sept 19:26) — matches the reported bug. But one authentic hang 2026-09-07 11:57 occurred under 1.18.29 with the ?? 300_000 default active (provider.ts:1795-1796, literal present in installed binary): ses_f845f8ac, delta 2ms, time.completed NULL. The default does not eliminate all hangs.
Two shapes, part rows confirm chunk-by-chunk persistence:
Provider was reachable: parallel stream on same API/model succeeded at the same second (logs 00:57Z). Zero timeout / retry / ECONNRESET lines in hang windows; only the later manual cancel + Aborted.
Code lead (dev@57ef382843, not ground truth): wrapSSE arms only if content-type contains "text/event-stream" (provider.ts:37-40) — a gateway answering otherwise (SSE-in-JSON, chunked) escapes the guard.
Workaround validated in prod:
{ "provider": { "<id>": { "options": { "chunkTimeout": 120000 } } } }Does wrapSSE's content-type check leave non-SSE streaming responses unguarded, and should the guard cover those?
Searched SSE hang, chunkTimeout — related: #46692, #43519.
Full DB row dumps (metadata only: version, model, provider, deltas, part counts) available on request.
rekram1-node commentedon Oct 2, 2026
Fixed in #46890 (v1.18.27) and #49229 (v2.0.22).