fix: return empty blob promise when ByteBlobLoader store is already consumed#28499
fix: return empty blob promise when ByteBlobLoader store is already consumed#28499robobun wants to merge 8 commits into
Conversation
|
Updated 1:29 AM PT - Mar 26th, 2026
❌ @robobun, your commit f4e468d has 3 failures in
🧪 To try this PR locally: bunx bun-pr 28499That installs a local version of the PR into your bun-28499 --bun |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Walkthrough
Changes
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
test/js/web/streams/blob-stream-bytes-consumed.test.ts
There was a problem hiding this comment.
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.
…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.
854aa01 to
eb93477
Compare
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
653fe44 to
cac5058
Compare
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
5c7e190 to
f384f94
Compare
…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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
|
Superseded by #28786, which landed on main with a cleaner fix (throws |
When
Response.bytes()consumes the body and thenresponse.body.bytes()is called on the underlying ReadableStream, the ByteBlobLoader's store is already null.toBufferedValuereturned.zerowithout 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:
ef61c39a47cb4c67Verification (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
.zeroreturn (which violated host-function error contract) withBlob.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.