Skip to content

fix: return empty blob promise when ByteBlobLoader store is already consumed#28499

Closed
robobun wants to merge 8 commits into
mainfrom
farm/8888056b/fix-blob-stream-bytes-consumed
Closed

fix: return empty blob promise when ByteBlobLoader store is already consumed#28499
robobun wants to merge 8 commits into
mainfrom
farm/8888056b/fix-blob-stream-bytes-consumed

Conversation

@robobun

@robobun robobun commented Mar 24, 2026

Copy link
Copy Markdown
Collaborator

When Response.bytes() consumes the body and then response.body.bytes() is called on the underlying ReadableStream, the ByteBlobLoader's store is already null. toBufferedValue returned .zero without setting a JS exception, causing the host function wrapper's assertion to panic with "Expected an exception to be thrown".

Fix: return a resolved promise with an empty blob instead of .zero, matching the behavior of an empty data source.

Crash fingerprint: ef61c39a47cb4c67


Verification (robobun, commit 854aa01): JS Lint passed. Buildkite build #41586 pipeline passed, cross-platform builds compiling. Prior build #41580 failures were unrelated tests (24364.test.ts react-tailwind tsc, bun-types.test.ts). Diff: 2 lines in ByteBlobLoader.zig replacing .zero return (which violated host-function error contract) with Blob.initEmpty(globalThis) + toPromise, matching the existing happy-path on line 180. Test spawns subprocess exercising consume-then-read-again path — would crash on main. All 3 review threads (claude, coderabbit) resolved: stderr changed from "pipe" to "inherit" in commit 854aa01. No TODO/FIXME/HACK. No unrelated changes.

@robobun

robobun commented Mar 24, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 1:29 AM PT - Mar 26th, 2026

@robobun, your commit f4e468d has 3 failures in Build #42189 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 28499

That installs a local version of the PR into your bun-28499 executable, so you can run:

bun-28499 --bun

@coderabbitai

coderabbitai Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

ByteBlobLoader.toBufferedValue now returns a promise-backed JSValue even when no underlying blob exists by creating an empty Blob. A test ensures calling resp.body.bytes() after await resp.bytes() does not crash.

Changes

Cohort / File(s) Summary
ByteBlob loader
src/bun.js/webcore/ByteBlobLoader.zig
toBufferedValue no longer returns a zero value when toAnyBlob(globalThis) yields no blob. It constructs an empty Blob.Any via Blob.initEmpty(globalThis) and returns empty.toPromise(globalThis, action), producing a promise-backed JSValue.
Regression test (streams)
test/js/web/streams/blob-stream-bytes-consumed.test.ts
Adds a test that runs a subprocess which consumes a Response with await resp.bytes() then calls await resp.body.bytes() inside a try/catch; the parent asserts the child prints OK and exits 0 to verify no crash.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: returning an empty blob promise instead of zero when a ByteBlobLoader's store is already consumed, directly addressing the root cause of the crash.
Description check ✅ Passed The description comprehensively covers the PR objectives with both required sections (What does this PR do? and How did you verify your code works?) properly filled with detailed technical context, verification steps, and testing information.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the fix is correct and the test nit is captured inline.

Extended reasoning...

Overview

This PR modifies src/bun.js/webcore/ByteBlobLoader.zig (2 lines) and adds a new regression test. The fix addresses a panic that occurred when toBufferedValue returned .zero without setting a JS exception, violating the host function wrapper contract.

Security risks

None. This is a crash fix in stream/blob plumbing with no auth, crypto, or permission surface.

Level of scrutiny

Low. The change is minimal: a single return .zero is replaced by constructing an empty blob and returning its promise, which matches the documented intended behavior (empty data source path). The fix mirrors the existing pattern used when a store is present (blob.toPromise(...)), just with an empty blob.

Other factors

The only issue found is a minor nit — the test captures stderr but never asserts expect(stderr).toBe(""), which is already posted as an inline comment. The core fix is correct and self-contained.

Comment thread test/js/web/streams/blob-stream-bytes-consumed.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/js/web/streams/blob-stream-bytes-consumed.test.ts`:
- Around line 20-22: The test creates a child process with stdout/stderr set to
"pipe" but never reads stderr, which can block; update the child-process options
object where stdout: "pipe", stderr: "pipe" is set to either set stderr:
"inherit" or "ignore", or keep stderr as "pipe" and explicitly consume it by
awaiting/reading proc.stderr.text() (or piping it to a sink) after
spawning—locate the options object and the spawned process variable (proc) in
blob-stream-bytes-consumed.test.ts and apply one of these fixes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3d8ba266-400c-463f-b588-a621e9a20434

📥 Commits

Reviewing files that changed from the base of the PR and between 943920a and 5243445.

📒 Files selected for processing (1)
  • test/js/web/streams/blob-stream-bytes-consumed.test.ts

Comment thread test/js/web/streams/blob-stream-bytes-consumed.test.ts Outdated
Comment thread test/js/web/streams/blob-stream-bytes-consumed.test.ts Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — previous concerns addressed, fix is correct.

Extended reasoning...

Overview

Two files changed: ByteBlobLoader.zig (2 lines) replaces a crash-inducing return .zero with an empty-blob promise, and blob-stream-bytes-consumed.test.ts adds a regression test for the exact consume-then-read-again scenario.

Core fix

The 2-line change in toBufferedValue follows the existing happy-path pattern exactly (matching line 180). Returning .zero without setting a JS exception violated the host function wrapper contract; returning empty.toPromise(globalThis, action) is the correct resolution.

Test quality

My two prior inline comments flagged (1) a missing stderr assertion and (2) a stderr pipe deadlock risk. Both are resolved in the current diff: stderr: "inherit" eliminates the pipe and the deadlock concern; the assertion gap is moot since there is no pipe to assert on.

Security risks

None — this is a bug fix for a null-store code path in stream internals with no auth, crypto, or permission surface.

Level of scrutiny

Low. The change is mechanically small, matches an existing pattern, is covered by a targeted regression test, and all prior reviewer concerns have been resolved.

robobun and others added 2 commits March 25, 2026 16:43
…onsumed

ByteBlobLoader.toBufferedValue returned .zero without setting a JS
exception when the underlying store was null (already consumed). The
host function wrapper asserts that an exception must be pending when
.zero is returned, causing a panic.

Return a resolved promise with an empty blob instead, matching the
behavior of an empty data source.
@robobun robobun force-pushed the farm/8888056b/fix-blob-stream-bytes-consumed branch from 854aa01 to eb93477 Compare March 25, 2026 16:43

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all prior review concerns addressed.

Extended reasoning...

Overview

Two files changed: a 2-line fix in ByteBlobLoader.zig replacing a .zero return with an empty-blob promise, and a new regression test in blob-stream-bytes-consumed.test.ts. The fix is minimal and self-contained.

Security risks

None. This is a crash fix in a ReadableStream blob loader path with no auth, crypto, or permission logic involved.

Level of scrutiny

Low. The patch is a straightforward "return a valid promise instead of an invalid zero value" correction. The host-function contract requires a JS exception or a valid JSValue; .zero violated that contract. Returning Blob.initEmpty(globalThis).toPromise(...) matches the existing happy-path pattern on the line above.

Other factors

Both of my prior inline comments (missing stderr assertion, pipe deadlock) and the coderabbit comment are marked resolved. The final diff shows stderr: "inherit" — the correct resolution given ASAN builds emit noisy stderr that cannot be asserted as empty. No outstanding reviewer comments remain. CI passed per the PR description.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all prior review threads resolved, fix is minimal and correct.

Extended reasoning...

Overview

This PR touches two files: src/bun.js/webcore/ByteBlobLoader.zig (2-line fix) and a new regression test. The fix addresses a crash where toBufferedValue returned .zero when the blob store was already consumed, violating the host-function contract that requires either a valid JSValue or a thrown exception.

Security risks

None. This is a defensive fix for a crash path in stream/blob internals with no auth, crypto, or permissions involvement.

Level of scrutiny

Low — the change is a surgical 2-line replacement that follows the exact same pattern as the existing happy-path on the line immediately above it (blob.toPromise(globalThis, action)). The fix is self-evidently correct: instead of returning .zero (which panics), it constructs an empty Blob and returns its promise, matching what an empty data source would produce.

Other factors

Two prior review threads (pipe deadlock and missing stderr assertion) were both resolved in commit 854aa01 by changing stderr: "pipe" to stderr: "inherit". The test now correctly avoids deadlock and asserts only on stdout and exit code. CI passed. No outstanding reviewer comments.

@robobun robobun force-pushed the farm/8888056b/fix-blob-stream-bytes-consumed branch from 653fe44 to cac5058 Compare March 25, 2026 22:06

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all prior review threads resolved, fix is correct.

Extended reasoning...

Overview

Two files changed: src/bun.js/webcore/ByteBlobLoader.zig (+2/-1) and a new regression test test/js/web/streams/blob-stream-bytes-consumed.test.ts (+28). The Zig change replaces an unconditional .zero return in toBufferedValue (triggered when the blob store is already null) with Blob.initEmpty(globalThis) wrapped in a promise, matching the existing happy-path on line 180. The test spawns a subprocess that exercises the consume-then-read-again path and verifies no crash.

Security risks

None. The change adds no new API surface, no user-controlled inputs, and no memory-unsafe operations. Blob.initEmpty is a well-established internal constructor.

Level of scrutiny

Low. This is a minimal targeted bug fix (2 lines) with a clear root cause (.zero violated the host-function error contract) and a precise, pattern-consistent remedy. The test is simple subprocess crash detection.

Other factors

Both prior inline review concerns (pipe deadlock risk, stderr assertion) have been addressed: the current diff uses stderr: "inherit", and all three review threads are marked resolved. The robobun CI failures are "Failed to create agent" infrastructure errors on Windows, unrelated to the code change. Build #41586 passed per the PR description.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all prior review threads are resolved and the fix is correct.

Extended reasoning...

Overview

Two files changed: src/bun.js/webcore/ByteBlobLoader.zig (2-line fix) and a new regression test. The fix replaces the invalid .zero return in toBufferedValue with Blob.initEmpty(globalThis) + toPromise, matching the existing happy-path on the line above it.

Security risks

None. This is a crash fix in blob stream handling with no auth, crypto, or permission-sensitive code paths involved.

Level of scrutiny

Low — the change is a minimal 2-line fix to a well-understood crash. The root cause (returning .zero without setting a JS exception violates the host-function error contract) is clearly documented, and the fix follows the established pattern already present in the same function.

Other factors

All three prior review threads (mine and coderabbitai's) are marked resolved. The test now uses stderr: "inherit" which addresses the pipe deadlock concern I raised. No bugs were found by the bug hunting system. CI failures in the timeline are infrastructure-related (failed to create agent) or pre-existing flaky tests unrelated to this change.

@robobun robobun force-pushed the farm/8888056b/fix-blob-stream-bytes-consumed branch from 5c7e190 to f384f94 Compare March 26, 2026 00:53
Comment thread test/js/web/streams/blob-stream-bytes-consumed.test.ts
robobun and others added 2 commits March 26, 2026 01:46
…onsumed

ByteBlobLoader.toBufferedValue returned .zero without setting a JS
exception when the underlying store was null (already consumed). The
host function wrapper requires either a valid JSValue or a pending
exception, causing a segfault/assertion failure.

Return a resolved promise with an empty blob instead.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the test has been updated to directly exercise the crash path, and the Zig fix is minimal and correct.

Extended reasoning...

Overview

Two files changed: ByteBlobLoader.zig (2 lines) and a new regression test. The fix replaces return .zero in toBufferedValue with Blob.initEmpty(globalThis) + toPromise, matching the existing happy-path return on line 180.

Security risks

None. This is a crash/assertion fix with no auth, crypto, or permissions involvement.

Level of scrutiny

Low. The change is a 2-line fix to an edge case (null store after prior consumption). The pattern is already established in the surrounding code; this is just filling in the missing branch.

Other factors

My previous two concerns have both been addressed: (1) the stderr: pipe without consumption was fixed by switching to inherit; (2) the test was rewritten from a subprocess-with-reader-loop approach to the direct crash reproducer — resp.bytes() then body.bytes() — which correctly nulls the store via toAnyBlob/detachStore before hitting toBufferedValue. The test now verifies the exact code path the fix covers. No bugs were found by the bug hunting system.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all prior concerns addressed, the fix and test are correct.

Extended reasoning...

Overview

Two files changed: a 2-line fix in ByteBlobLoader.zig replacing the invalid return .zero with Blob.initEmpty(globalThis) + toPromise, and a new regression test in test/js/web/streams/blob-stream-bytes-consumed.test.ts.

Fix correctness

The root cause is clear: toBufferedValue is a host-function that must return either a valid JSValue or throw a JS exception. Returning .zero (JSValue null) without an exception violates this contract, causing an assertion panic. The fix creates an empty Blob.Any and returns its promise, matching the existing happy-path on line 180 — this is the correct and minimal fix.

Test correctness

The test was rewritten in response to my previous review comment. It now uses the exact crash reproducer: const body = resp.body!; await resp.bytes(); const result = await body.bytes(). This sequence calls toAnyBlob twice — once during resp.bytes() (which detaches and nulls the store), and once during body.bytes() (which now hits the null-store branch). Without the fix this would crash; with it, it returns an empty Uint8Array.

Security risks

None. This is a null-check / error-contract fix in a stream buffering utility. No auth, crypto, or permission paths are touched.

Level of scrutiny

Low. The change is minimal (2 lines of Zig), self-contained, and the mechanism is straightforward. The test directly exercises the previously-crashing code path.

Other factors

All three previous reviewer threads (stderr pipe deadlock, stderr assertion, test not exercising null-store path) are marked resolved and have been addressed in the current revision. Build failures in the timeline were unrelated infrastructure issues (build #42173 was a transient zig build failure, not caused by this PR). The PR description confirms a passing CI build (#41586).

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the test now correctly reproduces the crash scenario and the Zig fix is minimal and correct.

Extended reasoning...

Overview

Two files changed: ByteBlobLoader.zig (2 lines: replace return .zero with an empty-blob promise) and a new regression test. The scope is narrow and self-contained.

Security risks

None. This is a crash fix in a blob-loading path with no auth, crypto, or permission-related code involved.

Level of scrutiny

Low — the fix is a one-liner correction to an error-contract violation (returning .zero without setting a JS exception panicked the host-function wrapper). The change is mechanical and matches the existing happy-path pattern on the line immediately above it.

Other factors

My 2026-03-26 inline comment flagged that the original test did not exercise the null-store code path (it used a reader loop that left this.store non-null and marked the stream disturbed, so toBufferedValue was never called). The test has since been updated to the exact reproducer I suggested: resp.bytes() detaches the store via toAnyBlob, then body.bytes() hits toBufferedValue with a null store — the fixed path. Both inline comments from my prior reviews are marked resolved. The stderr pipe issue flagged earlier was also resolved (changed to inherit in commit 854aa01). CI failures in the timeline are unrelated tests.

@robobun

robobun commented Apr 21, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #28786, which landed on main with a cleaner fix (throws ERR_BODY_ALREADY_USED instead of returning empty data). Closing.

@robobun robobun closed this Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant