Skip to content

Large session responses get cut mid-body under Bun (web UI: ClientError: Transport / Load failed) #50505

Closed

Description

@CalyxLab

Description

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.ts bind(), 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

  1. 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).
  2. 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
  3. 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.

Operating System

macOS (Apple Silicon), Bun 1.4.0

Activity

  1. Vibe-coding-nvm-delete-repo commented on Sep 22, 2026

    @Vibe-coding-nvm-delete-repo

    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.

  2. CalyxLab commented on Sep 22, 2026

    @CalyxLab
    Author

    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:

    1. 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
    
    1. 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.

  3. lsequeiraa commented on Oct 24, 2026

    @lsequeiraa

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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