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

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions