Skip to content

setResponse() truncates responses >16KB on Bun runtime due to premature res.end() #126

@Yuzu02

Description

@Yuzu02

Bug Description

setResponse() in src/adapters/node/request.ts calls res.end() inside the streaming for loop after the first successful res.write(). This causes responses larger than one ReadableStream chunk to be truncated.

On Node.js, new Response(largeString) typically delivers the entire string body as a single chunk via ReadableStream, so the bug is masked. On Bun runtime (tested on v1.3.11), ReadableStream chunks string bodies at 16,384 bytes (16 KB), causing any response larger than 16 KB to be silently truncated.

Affected Version

better-call@2.0.2

Root Cause

In dist/adapters/node/request.mjs, the setResponse function (line ~158):

async function next() {
    try {
        for (;;) {
            const { done, value } = await reader.read();
            if (done) break;
            if (!res.write(value))
                if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.LAMBDA_TASK_ROOT) continue;
                else {
                    res.once("drain", next);
                    return;
                }
            res.end(); // ← BUG: called inside the loop after first successful write
        }
    } catch (error) {
        cancel(error instanceof Error ? error : new Error(String(error)));
    }
}

The res.end() on the line after the if (!res.write(value)) block executes when res.write() returns true (buffer not full). This terminates the response after the first chunk, discarding all remaining data.

Expected Fix

res.end() should be called after the for loop exits (when done === true), not inside it:

async function next() {
    try {
        for (;;) {
            const { done, value } = await reader.read();
            if (done) break;
            if (!res.write(value))
                if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.LAMBDA_TASK_ROOT) continue;
                else {
                    res.once("drain", next);
                    return;
                }
        }
        res.end(); // ← Correct: after loop completes
    } catch (error) {
        cancel(error instanceof Error ? error : new Error(String(error)));
    }
}

Reproduction

Environment

  • Runtime: Bun 1.3.11 (Windows x64)
  • Framework: NestJS 11 with @thallesp/nestjs-better-auth@2.5.3
  • Plugin: better-auth@1.5.6 with openAPI({ theme: 'bluePlanet' })

Steps

  1. Configure Better Auth with the openAPI plugin
  2. Run the NestJS server using Bun as runtime: bun --bun run src/main.ts
  3. Navigate to /api/auth/reference
  4. The page is blank — Scalar UI does not render

Diagnosis

Fetching the raw response from the browser console:

const res = await fetch('/api/auth/reference');
const text = await res.text();
console.log(text.length);              // 16384 (exactly 16 KB — truncated)
console.log(text.endsWith('</html>')); // false
console.log(text.includes('cdn.jsdelivr.net')); // false — Scalar CDN script never sent

The HTML template (generated by better-auth's openAPI plugin) contains ~30 KB of content: the OpenAPI spec JSON + Scalar configuration script + Scalar CDN <script> tag. Only the first 16 KB is delivered — the response cuts off mid-JSON, so the </script> closing tag and the Scalar CDN script are never sent.

Comparison

  • Node.js runtime (same codebase, same config): /api/auth/reference renders correctly — full HTML delivered, Scalar UI works.
  • Bun runtime: Response truncated at 16,384 bytes — blank page.

Impact

This bug affects any better-call response larger than one ReadableStream chunk when running on Bun. Most auth endpoints return small JSON (<16 KB), so the bug is typically invisible. It manifests specifically with large responses like the OpenAPI reference page.

As Bun adoption increases (especially after the Anthropic acquisition), this will likely affect more users running better-auth on Bun.

Visual Evidence

Node.js runtime — Working ✅

Image

Scalar UI renders correctly with all endpoints visible. Full HTML (~30 KB) delivered successfully.

Bun runtime (v1.3.11) — Broken ❌

Image

Blank white page. The response is truncated at exactly 16,384 bytes (16 KB).

Response length:            16,384 bytes (truncated at exactly 16 KB)
Ends with </html>:          false
Contains Scalar CDN script: false
Closing </script> tags:     0 (expected 3)
Last 80 chars:              ...password of the user"},"image":{"type":"string","description":"The profile ima
Image

The response cuts off mid-JSON inside the OpenAPI spec. The </script> closing tag and the Scalar CDN <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"> are never delivered to the browser.


Metadata

Metadata

Assignees

No one assigned

    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