Skip to content

SSE stream silently dropped mid-response hangs session/subagents forever — chunkTimeout has no default on the openai path #37580

Description

@Jaaaky

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

  1. Start a task-tool subagent on the openai/ChatGPT subscription path with a
    long-reasoning model. Wait for message=stream providerID=openai in the log.
  2. 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
  3. Subagent spinner runs forever, prompts queue and are never delivered, no timeout
    or retry ever fires. ss -tinoe shows the socket idle with no timer armed.
  4. 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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions