You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Large session responses get cut mid-body under Bun (web UI: ClientError: Transport / Load failed) #50505
Opening a large session in the web UI fails with ClientError: Transport / cause TypeError: Load failed. The server closes the connection while the response body is still draining to a client that reads slower than ~1 MB/s.
GET /api/session/{id}/message returns the newest 50 messages of a session as one JSON body (~9.5 MB for my session). The body is written with a single res.end(body). Under Bun, server.keepAliveTimeout (default 5s, advertised as Keep-Alive: timeout=5) destroys the socket mid-drain — there are no further write() calls to reset the timer. Node is not affected. Chunked HttpServerResponse.stream responses are not affected (periodic writes reset the timer).
I confirmed this with a minimal repro containing no opencode code: Bun + @effect/platform-node returning a 9.5 MB string body, client at 600 KB/s → connection cut at ~9 s (~5.4 MB sent). Same server under Node completes. Setting server.keepAliveTimeout = 0 before listen fixes it.
Repro against opencode v2.0.12 (dev serve from source, slow client):
$ curl --limit-rate 600K -u opencode:$PW http://127.0.0.1:4990/api/session/<sid>/message
curl: (18) transfer closed with 3968156 bytes remaining to read
# response declared content-length: 9517142
With server.keepAliveTimeout = 0 in packages/server/src/process.tsbind(), the same request completes: bytes=9517142 time=16.2s. Packages/server tests pass (58 pass, 0 fail).
Plugins
None relevant
OpenCode version
2.0.12 (also reproduces from source at v2 branch, commit 651529d)
Steps to reproduce
opencode serve (or bun dev serve), open a session whose newest-50-messages response is several MB (large base64 images in tool results make this easy).
Fetch it with a client slower than ~1 MB/s: curl --limit-rate 600K -u opencode:$PW http://127.0.0.1:4990/api/session/<sid>/message
Transfer is cut mid-body (curl: (18) transfer closed with N bytes remaining to read); the web UI shows ClientError: Transport / TypeError: Load failed instead of the session.
Fast clients on loopback never hit it — that's why most sessions work and only big ones fail.
Not reproduced on a controlled test (desktop 2.0.12 + bundled CLI 2.0.12, Windows 11 10.0.26200), in case it helps narrow this to a client-side path.
Method: authenticate to the local server, fetch the largest session payload available (GET /api/session/<id>/message?limit=200), repeat 6x, check byte count and full JSON parse; then repeat with a second HTTP client and compare SHA256.
Result: 6,815,501-byte payload, six consecutive 200s, identical byte count each time, JSON parsed completely each time. Two independent clients received byte-identical bodies (sha256 979740661f51500fc1ea6f8f08fd5b4723a85cbb1d90dafb1a4885a88532ab06), 0.15-0.42 s.
Limits: this exercises the HTTP transport, not the browser-side fetch inside the desktop renderer; the original session and Bun version were not available. If it still reproduces, the response's Content-Length vs received bytes at the moment of failure (and whether curl on the same session also fails) would separate server from client.
Observation, not a claim: PowerShell's Invoke-WebRequest hung once on the same request while curl and Node completed in under half a second - possibly a client-stack artifact.
Thanks for testing! That result actually matches the diagnosis rather than contradicting it: the socket is only killed when the body drain takes longer than the 5s keep-alive window (client slower than ~1 MB/s). A fast local client finishes the body well inside that window — that's exactly why most sessions work and only large ones on slow paths fail.
Two deterministic repros that avoid the timing lottery:
Throttle the client against a large session:
curl --limit-rate 600K -u opencode:$PW http://127.0.0.1:4990/api/session/<bigSessionId>/message
# curl: (18) transfer closed with N bytes remaining to read
# response declares content-length: 9517142
Minimal repro with no opencode code: Bun + @effect/platform-node returning a 9.5 MB string body to a 600 KB/s client → cut at ~9 s (~5.4 MB sent). Same server under Node completes, and chunked HttpServerResponse.stream under Bun completes. Setting server.keepAliveTimeout = 0 fixes it (Bun 1.4.x, macOS arm64 — given your Windows result, platform/Bun version may matter).
The change in #50507 only disables that timer, so it's a no-op wherever the bug doesn't manifest.
This is fixed from v2.1.3: releases are now built with Bun 1.4.4 (packageManager went from bun@1.4.2 in v2.1.2 to bun@1.4.4 in v2.1.3, and the v2.1.4 binary reports Bun v1.4.4), which includes the node:http fix from Bun 1.4.3 (oven-sh/bun#43557).
Checked against a v2.1.4 service: an 11.9 MB GET /api/session/<id>/message?limit=200 read with curl --limit-rate 650k arrives complete in 17 s. On v2.0.29 the same page read at 1.5 MB/s, 650 KB/s and 300 KB/s was cut every time. So this one can probably be closed.
Activity
Vibe-coding-nvm-delete-repo commentedon Sep 22, 2026
Not reproduced on a controlled test (desktop 2.0.12 + bundled CLI 2.0.12, Windows 11 10.0.26200), in case it helps narrow this to a client-side path.
Method: authenticate to the local server, fetch the largest session payload available (
GET /api/session/<id>/message?limit=200), repeat 6x, check byte count and full JSON parse; then repeat with a second HTTP client and compare SHA256.Result: 6,815,501-byte payload, six consecutive 200s, identical byte count each time, JSON parsed completely each time. Two independent clients received byte-identical bodies (
sha256 979740661f51500fc1ea6f8f08fd5b4723a85cbb1d90dafb1a4885a88532ab06), 0.15-0.42 s.Limits: this exercises the HTTP transport, not the browser-side
fetchinside the desktop renderer; the original session and Bun version were not available. If it still reproduces, the response'sContent-Lengthvs received bytes at the moment of failure (and whethercurlon the same session also fails) would separate server from client.Observation, not a claim: PowerShell's
Invoke-WebRequesthung once on the same request whilecurland Node completed in under half a second - possibly a client-stack artifact.CalyxLab commentedon Sep 22, 2026
Thanks for testing! That result actually matches the diagnosis rather than contradicting it: the socket is only killed when the body drain takes longer than the 5s keep-alive window (client slower than ~1 MB/s). A fast local client finishes the body well inside that window — that's exactly why most sessions work and only large ones on slow paths fail.
Two deterministic repros that avoid the timing lottery:
@effect/platform-nodereturning a 9.5 MB string body to a 600 KB/s client → cut at ~9 s (~5.4 MB sent). Same server under Node completes, and chunkedHttpServerResponse.streamunder Bun completes. Settingserver.keepAliveTimeout = 0fixes it (Bun 1.4.x, macOS arm64 — given your Windows result, platform/Bun version may matter).The change in #50507 only disables that timer, so it's a no-op wherever the bug doesn't manifest.
lsequeiraa commentedon Oct 24, 2026
This is fixed from v2.1.3: releases are now built with Bun 1.4.4 (
packageManagerwent frombun@1.4.2in v2.1.2 tobun@1.4.4in v2.1.3, and the v2.1.4 binary reports Bun v1.4.4), which includes thenode:httpfix from Bun 1.4.3 (oven-sh/bun#43557).Checked against a v2.1.4 service: an 11.9 MB
GET /api/session/<id>/message?limit=200read withcurl --limit-rate 650karrives complete in 17 s. On v2.0.29 the same page read at 1.5 MB/s, 650 KB/s and 300 KB/s was cut every time. So this one can probably be closed.