Summary
Large tool outputs can kill the event stream with Upstream idle timeout exceeded when the provider takes a long time to process a large tool_result before the next SSE chunk is emitted.
Reproduction
- Run OpenCode against a slower LLM provider or a model that spends noticeable time processing tool results.
- Trigger a tool that returns a large payload (roughly 50KB+, more obvious around 150KB+).
- Wait for the model to consume the tool result and resume streaming.
- The SSE stream can die before the next event arrives, with
Upstream idle timeout exceeded.
Expected
The SSE connection should stay alive while the upstream model is still legitimately processing the previous tool result.
Actual
The SSE connection can be closed during the provider thinking gap, even though the request is still active and the model has not finished.
Suspected root cause
This looks like an idle-timeout problem during the silent gap between SSE events:
- OpenCode already emits heartbeat events on the SSE routes every 10 seconds in the current
dev branch handlers.
- But if the underlying HTTP server or adapter still enforces an idle timeout on the streaming request, a long gap while the provider processes a large tool result can still kill the connection before the next real event is flushed.
- The failure mode is especially visible with oversized tool outputs.
While investigating, I also noticed current dev appears to serve HTTP via node:http + Effect routes rather than the older Bun-specific server path, so this may affect released builds, another runtime path, or a remaining adapter layer rather than the exact current dev implementation.
Proposed fix
If any Bun-based streaming adapter is still in the serving path for released builds, disable idle timeout per streaming request, e.g. server.timeout(req, 0) for SSE connections. More generally: ensure the SSE/event-stream route has no transport-level idle timeout while a request is still active.
Workarounds
Current mitigations on the client/project side:
- Cap
tool_output.max_lines / tool_output.max_bytes so tool payloads stay smaller.
- Prefer chunked reads instead of one huge file dump.
- For long-running shell commands, emit low-frequency heartbeat output.
Source notes from inspection
Current dev branch evidence:
packages/opencode/src/server/server.ts uses createServer() from node:http.
- SSE handlers at:
packages/opencode/src/server/routes/instance/httpapi/handlers/event.ts
packages/opencode/src/server/routes/instance/httpapi/handlers/global.ts
- Both handlers emit a 10-second
server.heartbeat event.
- I did not find a current
server.timeout(req, 0) call in the inspected server/SSE path.
If helpful, I can also test specific released binaries/builds and narrow down whether this is only present in packaged releases.
Summary
Large tool outputs can kill the event stream with
Upstream idle timeout exceededwhen the provider takes a long time to process a largetool_resultbefore the next SSE chunk is emitted.Reproduction
Upstream idle timeout exceeded.Expected
The SSE connection should stay alive while the upstream model is still legitimately processing the previous tool result.
Actual
The SSE connection can be closed during the provider thinking gap, even though the request is still active and the model has not finished.
Suspected root cause
This looks like an idle-timeout problem during the silent gap between SSE events:
devbranch handlers.While investigating, I also noticed current
devappears to serve HTTP vianode:http+ Effect routes rather than the older Bun-specific server path, so this may affect released builds, another runtime path, or a remaining adapter layer rather than the exact currentdevimplementation.Proposed fix
If any Bun-based streaming adapter is still in the serving path for released builds, disable idle timeout per streaming request, e.g.
server.timeout(req, 0)for SSE connections. More generally: ensure the SSE/event-stream route has no transport-level idle timeout while a request is still active.Workarounds
Current mitigations on the client/project side:
tool_output.max_lines/tool_output.max_bytesso tool payloads stay smaller.Source notes from inspection
Current
devbranch evidence:packages/opencode/src/server/server.tsusescreateServer()fromnode:http.packages/opencode/src/server/routes/instance/httpapi/handlers/event.tspackages/opencode/src/server/routes/instance/httpapi/handlers/global.tsserver.heartbeatevent.server.timeout(req, 0)call in the inspected server/SSE path.If helpful, I can also test specific released binaries/builds and narrow down whether this is only present in packaged releases.