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
- Configure Better Auth with the
openAPI plugin
- Run the NestJS server using Bun as runtime:
bun --bun run src/main.ts
- Navigate to
/api/auth/reference
- 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 ✅
Scalar UI renders correctly with all endpoints visible. Full HTML (~30 KB) delivered successfully.
Bun runtime (v1.3.11) — Broken ❌
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
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.
Bug Description
setResponse()insrc/adapters/node/request.tscallsres.end()inside the streamingforloop after the first successfulres.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 viaReadableStream, so the bug is masked. On Bun runtime (tested on v1.3.11),ReadableStreamchunks 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.2Root Cause
In
dist/adapters/node/request.mjs, thesetResponsefunction (line ~158):The
res.end()on the line after theif (!res.write(value))block executes whenres.write()returnstrue(buffer not full). This terminates the response after the first chunk, discarding all remaining data.Expected Fix
res.end()should be called after theforloop exits (whendone === true), not inside it:Reproduction
Environment
@thallesp/nestjs-better-auth@2.5.3better-auth@1.5.6withopenAPI({ theme: 'bluePlanet' })Steps
openAPIpluginbun --bun run src/main.ts/api/auth/referenceDiagnosis
Fetching the raw response from the browser console:
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
/api/auth/referencerenders correctly — full HTML delivered, Scalar UI works.Impact
This bug affects any
better-callresponse 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-authon Bun.Visual Evidence
Node.js runtime — Working ✅
Scalar UI renders correctly with all endpoints visible. Full HTML (~30 KB) delivered successfully.
Bun runtime (v1.3.11) — Broken ❌
Blank white page. The response is truncated at exactly 16,384 bytes (16 KB).
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.