Skip to content

fix(api): stream uploads to the file-server with fetch; bump Bun to 1.4.2 (fixes silent upload truncation) - #174

Merged
danny-avila merged 2 commits into
LibreChat-AI:mainfrom
mihidumh:fix/upload-stream-bun
Sep 10, 2026
Merged

fix(api): stream uploads to the file-server with fetch; bump Bun to 1.4.2 (fixes silent upload truncation)#174
danny-avila merged 2 commits into
LibreChat-AI:mainfrom
mihidumh:fix/upload-stream-bun

Conversation

@mihidumh

Copy link
Copy Markdown
Contributor

Problem

POST /v1/upload (and /upload/batch) can store a short object and still answer success. A 20 MiB upload lands 32 KiB–800 KiB short, different every time; 1 MiB is fine. No service logs an error, and /v1/download faithfully returns the short object.

Root cause

The api forwards each busboy file part to the file-server with axios.put(url, fileStream). axios sends a stream body through node:http's ClientRequest as a chunked body. On Bun 1.3.x (oven/bun:1.3.14 in every Dockerfile) that client drops queued trailing chunks and still sends a valid chunked terminator — oven-sh/bun#39752, fixed in Bun 1.4.0 by oven-sh/bun#31587.

How it was pinned down (prod-like AKS cluster, kubectl port-forward, never through a proxy):

probe result
20 MiB PUT straight to the file-server, 5× chunked + 5× Content-Length intact 10/10 → file-server + MinIO are fine
replica of the /upload handler (busboy → axios.put) run inside the api pod short 6/6 (through a slow port-forward it never reproduces — it needs in-cluster timing)
same replica with a counting transport every byte reached ClientRequest.write(), end() called last, write() never returned false — stored short 4/4 (axios) and 4/4 (bare http.request + pipe)
same replica → Bun.serve byte-counting sink in the same pod sink received 20,709,036 of 20,971,520 with transfer-encoding: chunked → bytes lost on the wire
same replica sending with fetch + Readable.toWeb(file) intact 4/4
same replica under a Bun 1.4.2 binary intact 8/8
synthetic repro (Readable → http.request, no busboy, no axios) Bun 1.3.10/1.3.14: first run short by one or more chunks; Node 24 and Bun 1.4.2: intact

The shortfall is always constant + k × 32 KiB — whole trailing socket chunks.

Fix

  1. router.ts: putFileToFileServer() sends the busboy part with the global fetch and a web ReadableStream (duplex: 'half'), replacing both axios.put stream calls. Runtime-independent (Bun native fetch; undici on Node). busboy's limits.fileSize still caps the part, so no separate body-length guard is needed. Verified by hot-loading the patched file into a running api pod: 8/8 intact at 20 MiB, 1 MiB intact.
  2. Dockerfiles: oven/bun:1.3.141.4.2 everywhere, as the second line of defence for the remaining node:http clients (e.g. runtime-session/checkpoint.ts).

tsc --noEmit reports no error in the changed file (the 5 pre-existing errors elsewhere are untouched); upload-session, files and file-authorization tests pass.

Not affected: sandbox output files go sandbox → egress-gateway → file-server, and the gateway already forwards them with native fetch.

🤖 Generated with Claude Code

mihidumh and others added 2 commits September 10, 2026 10:15
…r node:http

On Bun, node:http's ClientRequest can drop the tail of a chunked request
body: write() accepts every byte and end() is called after the last write,
yet the peer receives 32 KiB-800 KiB less. The file-server then stores a
short object and reports success, so a 20 MiB upload comes back corrupt
with no error anywhere (Bun 1.3.10-1.3.14; 1 MiB is unaffected).

Send the busboy file part with the global fetch and a web ReadableStream
instead. Bun's native fetch and Node's undici stream the body intact.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Bun 1.3.x's node:http client drops the tail of chunked request bodies
under load (see the previous commit). Bun 1.4.2 streams them intact in
the same test, so the base image bump is the second line of defence for
every remaining node:http client in the services.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@danny-avila

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-10T03:08:19.856403Z 6c5586a Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 6c5586a901

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila
danny-avila merged commit 1198759 into LibreChat-AI:main Sep 10, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants