Skip to content

node:buffer: fix six Node compatibility divergences - #32784

Merged
Jarred-Sumner merged 8 commits into
mainfrom
farm/03ab414d/node-buffer-divergences
Jun 26, 2026
Merged

Jarred-Sumner merged 8 commits into
mainfrom
farm/03ab414d/node-buffer-divergences

Conversation

@robobun

@robobun robobun commented Jun 26, 2026 •

Copy link
Copy Markdown
Collaborator

Six node:buffer behaviors that disagree with Node, found by comparing Bun and Node output over the same inputs. All are exercised by new or strengthened tests in test/js/node/buffer.test.js; every expected value was verified against node v26.3.0.

Buffer.byteLength under-counts unpaired surrogates

const s = "\ud800a".repeat(17);
Buffer.byteLength(s, "utf8");    // bun: 51   node: 68
Buffer.from(s, "utf8").length;   // bun: 68   node: 68

Bun__encoding__byteLengthUTF16AsUTF8 (which carried a // TODO(@190n) handle unpaired surrogates) went through element_length_utf16_into_utf8, which called simdutf's non-validating utf8_length_from_utf16le. That function charges every code unit in 0xD800..0xDFFF two bytes, half of an assumed 4-byte pair. The encoder replaces each unpaired surrogate with U+FFFD (three bytes), so byteLength disagreed with Buffer.from(s).length. That is an under-allocation footgun for the common Buffer.allocUnsafe(Buffer.byteLength(s)) + buf.write(s) pattern (the write is bounds-checked, so the result is truncation, not a memory error).

simdutf already ships the correct function, utf8_length_from_utf16le_with_replacement (SIMD-accelerated; .count is documented to be valid even when the error field is SURROGATE). It just was not exposed through the FFI shim. This adds the binding and points element_length_utf16_into_utf8 at it. The new value is always greater than or equal to the old one and exactly matches the encoder's output, so every existing caller that used it as an allocation size or an expected-written length is either unchanged (valid UTF-16) or corrected (lone surrogates). As a side effect, TextEncoder.encode no longer falls back to a second allocation for lone-surrogate input.

lastIndexOf with utf16le does a raw byte search

// "a" in utf16le is the byte pair [0x61, 0x00]; it occurs here only at byte offset 1
Buffer.from([0x00, 0x61, 0x00, 0x00]).lastIndexOf("a", undefined, "utf16le");
// bun: 1   node: -1

Node searches in uint16_t units, so a UCS2 match can only start on an even byte offset. Bun's forward indexOf/includes already do this via indexOf16, but in indexOfString/indexOfBuffer the last branch was taken before the ucs2 branch, so backward searches always fell through to the byte-level std::find_end. This adds a lastIndexOf16 mirroring indexOf16 and moves the ucs2 branch outward.

Empty haystack short-circuits before the empty-needle logic

Buffer.alloc(0).indexOf(Buffer.alloc(0)); // bun: -1   node: 0

computeIndexOfRange already implements Node's semantics here (an empty needle is found at the clamped byteOffset; a non-empty needle in an empty haystack is -1), but if (byteLength == 0) return -1; in the dispatcher and three per-type re-checks ran first. They are removed, so the shared helper is the single source of truth. The byteOffset coercion (which can run user valueOf) is also no longer skipped for an empty buffer, matching Node.

fill() silently truncates a fractional offset

Buffer.alloc(4, "x").fill("a", 1.5);
// bun:  <Buffer 78 61 61 61>
// node: throws ERR_OUT_OF_RANGE 'The value of "offset" is out of range. It must be an integer. Received 1.5'

offset and end went through validateNumber (which has no integer check) and were then truncated with toLength. Node routes both through validateOffset, which is validateInteger. Bun::V::validateInteger already exists and is used the same way by compare() in the same file.

swap16/swap32/swap64 throw the wrong code

try { Buffer.alloc(3).swap16(); } catch (e) { e.code }
// bun: "ERR_OUT_OF_RANGE"   node: "ERR_INVALID_BUFFER_SIZE"

The message was already correct, but throwNodeRangeError hardcodes ERR_OUT_OF_RANGE. Node uses ERR_INVALID_BUFFER_SIZE (a RangeError), which did not exist in ErrorCode.ts; it is added there.

writeBig[U]Int64LE/BE with a negative offset throws the wrong code

Buffer.alloc(16).writeBigInt64LE(1n, -1);
// bun:  ERR_BUFFER_OUT_OF_BOUNDS: "offset" is outside of buffer bounds
// node: ERR_OUT_OF_RANGE: The value of "offset" is out of range. It must be >= 0 and <= 8. Received -1

Node's boundsError puts a negative offset in the same >= 0 and <= max bucket as a too-large one; ERR_BUFFER_OUT_OF_BOUNDS is reserved for a buffer that is too short to hold 8 bytes at all. validateOffsetBigInt64's two negative branches now issue the same OUT_OF_RANGE the too-large path already used, and the integer-ness check moves ahead of the sign check so that -1.5 reports an integer, also matching Node.

During review this grew into a full reordering of validateOffsetBigInt64 to match Node's validateNumber then boundsError sequence exactly: the offset's type is checked first, then its integer-ness (using Math.floor(value) !== value semantics, so NaN and fractions report an integer while +-Infinity do not), then the too-short buffer, then the range. This makes Buffer.alloc(7).writeBigInt64LE(1n, "x") a TypeError ERR_INVALID_ARG_TYPE and Buffer.alloc(7).writeBigInt64LE(1n, 1.5) an ERR_OUT_OF_RANGE, as in Node, instead of ERR_BUFFER_OUT_OF_BOUNDS for both. Every combination is in the test block and verified against Node v26.3.0.

Verification

On the released Bun the 8 new or strengthened test blocks all fail (16 failures across buffer.test.js's two parameterized suites; the other 501 tests are unchanged). With this change:

  • bun bd test test/js/node/buffer.test.js: 516 pass, 0 fail
  • Node's upstream test-buffer-{indexof,fill,swap,bytelength,bigint64}.js all still pass
  • bun bd test test/js/web/encoding/: 2433 pass, 0 fail (element_length_utf16_into_utf8 also feeds TextEncoder)
  • bun bd test test/js/node/buffer-utf16.test.ts test/js/node/buffer-indexOf-detach.test.ts test/js/node/buffer-copy-fill-detach.test.ts test/js/node/buffer-compare-bounds.test.ts test/js/node/buffer-concat.test.ts: 64 pass, 0 fail

Related

#32383 works around the same element_length_utf16_into_utf8 under-count at TextEncoder's call site (it caused a double Uint8Array allocation per encode() for large lone-surrogate input). The root-cause fix here resolves that too: verified with 20 encode() calls on a 100,000-unit string containing a lone surrogate going from 40 live Uint8Arrays to 20, with no change to TextEncoder.rs.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

UTF-16LE byte-length sizing now uses replacement semantics for unpaired surrogates. Buffer swap methods throw a new invalid-buffer-size code. Buffer fill, BigInt offset checks, and UCS2/empty-buffer search behavior are updated, with matching Node-style tests.

Changes

Buffer UTF-16 and validation changes

Layer / File(s) Summary
Replacement-aware UTF-16 sizing
src/bun_core/lib.rs, src/simdutf_sys/bun-simdutf.cpp, src/simdutf_sys/simdutf.rs, src/runtime/webcore/encoding.rs, test/js/node/buffer.test.js
UTF-16LE length helpers and the fixed-buffer UTF-8 conversion now use replacement-aware sizing, and the duplicate UTF-16 surrogate TODO comments are removed.
Invalid buffer size code
src/jsc/ErrorCode.rs, src/jsc/bindings/ErrorCode.ts, src/jsc/bindings/JSBuffer.cpp, test/js/node/buffer.test.js
ERR_INVALID_BUFFER_SIZE is added to the error-code tables, and swap16/swap32/swap64 throw that code for invalid buffer sizes; tests assert the new code and RangeError class.
Integer offset validation
src/jsc/bindings/JSBuffer.cpp, test/js/node/buffer.test.js
fill() and BigInt write offset validation now reject fractional values as non-integers and report negative offsets as ERR_OUT_OF_RANGE; tests cover the updated messages and existing bounds errors.
UCS2 search boundaries
src/jsc/bindings/JSBuffer.cpp, test/js/node/buffer.test.js
UCS2 indexOf/lastIndexOf dispatch now uses code-unit-aware helpers, and empty-buffer search handling relies on computeIndexOfRange; tests cover empty needles and odd/even byte offsets.

Possibly related PRs

  • oven-sh/bun#31385: Shares the UTF-16→UTF-8 length path in src/bun_core/lib.rs and the fixed-buffer sizing logic used by copy_utf16_into_utf8.
  • oven-sh/bun#31532: Also changes replacement handling in copy_utf16_into_utf8 and exercises the same unpaired-surrogate conversion behavior.
  • oven-sh/bun#32626: Also changes src/jsc/bindings/JSBuffer.cpp Buffer validation and search behavior, including error classification and UCS2 boundary handling.

Suggested reviewers

  • Jarred-Sumner
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: fixing Node buffer compatibility divergences.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly explains the changes and includes concrete verification details, satisfying the template’s intent.

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

@robobun

robobun commented Jun 26, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 6:17 PM PT - Jun 26th, 2026

❌ @robobun, your commit b87f06f has 2 failures in Build #65193 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 32784

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

bun-32784 --bun

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. TextEncoder: avoid double Uint8Array allocation on large UTF-16 with lone surrogates #32383 - Both fix element_length_utf16_into_utf8 in src/bun_core/lib.rs to use simdutf's utf8_length_from_utf16le_with_replacement instead of the non-replacement variant for lone surrogates; this PR subsumes that fix as a side effect of the Buffer.byteLength correction

🤖 Generated with Claude Code

@robobun

robobun commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator Author

Regarding the duplicate flag above: #32383 is not a duplicate, and it does not change element_length_utf16_into_utf8 (the bot's description is inaccurate). It works around the under-count at TextEncoder's call site instead, by dropping the pre-sized fast path for large input.

They are related, though: the byteLength fix here corrects the shared length helper, which is the root cause of the double allocation #32383 is about. Verified on this branch with #32383's own repro shape: 20 TextEncoder().encode() calls on a 100,000-unit string containing a lone surrogate go from 40 live Uint8Arrays (two per call) down to 20 (one per call), with no change to TextEncoder.rs.

Left a note on #32383 with the details so a maintainer can decide which shape to land.

Comment thread src/jsc/bindings/ErrorCode.ts Resolved

@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.

The ErrorCode.ts/ErrorCode.rs append-only fix in 497ae87 looks correct (both tables now end with ERR_INVALID_BUFFER_SIZE at index 328, COUNT=329). Deferring to a human for the rest — six independent behavioral changes across C++/Rust/FFI in hot Buffer paths, plus the overlap with #32383, warrant maintainer eyes.

Extended reasoning...

Overview

This PR fixes six separate node:buffer Node-compat divergences, touching 8 files across three languages: C++ (JSBuffer.cpp — fill, indexOf/lastIndexOf, swap16/32/64, validateOffsetBigInt64), Rust (bun_core/lib.rs, simdutf_sys/simdutf.rs, runtime/webcore/encoding.rs, jsc/ErrorCode.rs), the simdutf C++ FFI shim, the ErrorCode.ts codegen source, and ~130 lines of new tests in buffer.test.js.

Since my previous review, commit 497ae87 addressed the blocking issue I raised: ERR_INVALID_BUFFER_SIZE is now appended to the tail of ErrorCode.ts (not inserted alphabetically), and src/jsc/ErrorCode.rs was updated in lockstep — discriminant 328, COUNT bumped to 329, the ERR_-prefixed alias added, and the CODE_STR table extended. The Rust↔C++ index alignment is preserved.

Security risks

None identified. The changes are Node-compat behavioral corrections (error codes/messages, search alignment, length computation). The byteLength fix only ever increases the reported length, so it cannot introduce under-allocation; the PR description notes the encoder write path was already bounds-checked. No auth, crypto, or permission surfaces are touched.

Level of scrutiny

This warrants human review despite no bugs found this round. Reasons: (1) six logically independent fixes bundled together, each with its own edge cases; (2) element_length_utf16_into_utf8 is a shared helper feeding both Buffer.byteLength and TextEncoder — its semantics change globally; (3) the indexOf dispatcher removes four byteLength == 0 early-returns, relying on computeIndexOfRange and the per-type helpers to handle empty haystacks safely (including with possibly-null typedVector); (4) lastIndexOf16 reinterprets uint8_t* as uint16_t* (mirrors existing indexOf16, but worth a glance for alignment assumptions); (5) the fill() change swaps validateNumber+toLength for validateInteger with an out-param, which changes coercion semantics for non-number inputs too.

Other factors

Test coverage is thorough — every fix has a dedicated test block verified against Node v26.3.0, and the PR description reports the relevant upstream Node test files and Bun's encoding suite still pass. There is an acknowledged overlap with #32383 (same root cause, different fix shape) that a maintainer should resolve before landing either.

robobun added 2 commits June 26, 2026 20:38
- Buffer.byteLength(s, "utf8") under-counted strings containing unpaired
  surrogates: it used simdutf's non-validating UTF-16 length, which
  charges every surrogate code unit 2 bytes, while the encoder writes
  U+FFFD (3 bytes). So byteLength disagreed with Buffer.from(s).length,
  an under-allocation footgun for code that sizes a buffer with one and
  writes with the other. Expose simdutf's
  utf8_length_from_utf16le_with_replacement and use it in
  element_length_utf16_into_utf8 so the length helper matches the
  encoder for ill-formed UTF-16.

- Buffer#lastIndexOf(value, offset, "utf16le") did a raw byte search.
  Node searches in uint16_t units (a match can only start on an even
  byte offset); the forward indexOf already did. Add a uint16 backward
  search and route the ucs2 + last case through it.

- An empty haystack returned -1 before the empty-needle logic could run,
  so Buffer.alloc(0).indexOf(Buffer.alloc(0)) was -1 instead of 0.
  Remove the short-circuits; computeIndexOfRange already handles both
  the empty haystack and the empty needle correctly.

- Buffer#fill() validated offset and end with validateNumber (no integer
  check) and then truncated them with toLength, so fill("a", 1.5)
  silently filled from 1. Node uses validateOffset (= validateInteger)
  and throws ERR_OUT_OF_RANGE "It must be an integer".

- Buffer#swap16/32/64 threw with code ERR_OUT_OF_RANGE. Node uses
  ERR_INVALID_BUFFER_SIZE, which did not exist in ErrorCode.ts; add it.

- writeBig[U]Int64LE/BE with a negative offset threw
  ERR_BUFFER_OUT_OF_BOUNDS. Node's boundsError reports it as
  ERR_OUT_OF_RANGE ">= 0 and <= max", the same as a too-large offset.
The `errors` array in ErrorCode.ts is index-aligned with the
hand-maintained Rust mirror in src/jsc/ErrorCode.rs, and the C++
ErrorCode enum plus its errors[] table are regenerated from array
position. Inserting the new code alphabetically shifted every code past
the insertion point by one, so any error thrown from Rust with a
discriminant at or above it surfaced with the previous entry's
constructor, code, and name. For example, a Rust-thrown
ERR_ZLIB_INITIALIZATION_FAILED became a TypeError with
code ERR_WORKER_UNSUPPORTED_OPERATION, which is what made
test-zlib-brotli.js and test-zlib-zstd.js fail in CI.

Move the entry to the documented append-only tail and register it in
the Rust mirror: the discriminant const, the ERR_* alias, the CODE_STR
table, and COUNT.
@robobun
robobun force-pushed the farm/03ab414d/node-buffer-divergences branch from 497ae87 to 8d02ea4 Compare June 26, 2026 20:39
Comment thread src/jsc/bindings/JSBuffer.cpp Resolved
Comment thread src/jsc/bindings/JSBuffer.cpp Resolved
…tale comments

validateOffsetBigInt64's integer-ness check used std::fmod, which is NaN
for +-Infinity, so those reported "an integer" where Node's boundsError
(whose test is Math.floor(value) !== value) reports the ">= 0 and <= max"
range. Use std::floor to match, and range-check the double BEFORE
truncating so a non-finite value never reaches truncateDoubleToUint64;
the now-redundant post-truncation check is folded into it.

Also update two comments in fillBody that still named validateNumber.
Comment thread src/jsc/bindings/JSBuffer.cpp Resolved
Comment thread src/runtime/webcore/encoding.rs Resolved
refetchBufferState's doc said a detached buffer is treated as an empty
one (both returning -1). This PR decoupled those: a detached haystack
still returns -1 but an empty one is handled by computeIndexOfRange and
can return 0 for an empty needle, so the equivalence no longer holds.

Also delete the leftover unpaired-surrogate TODO above the Latin-1
byteLength helper. It was a misplaced duplicate of the UTF-16 one this
PR resolved; surrogates do not exist in Latin-1 input.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/jsc/bindings/JSBuffer.cpp (2)

1320-1333: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Condense the expanded comment blocks.

These updated comments exceed the repo’s 3-line limit; keep only the durable invariant and move detailed Node-trace/history into the PR description. As per coding guidelines, “Keep code comments to 3 lines max.”

Also applies to: 1364-1385, 1704-1707, 2689-2701

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/jsc/bindings/JSBuffer.cpp` around lines 1320 - 1333, The comment in
JSBuffer-related code is too long and exceeds the 3-line limit; trim the
expanded explanation down to the durable invariant only and move the
Node-trace/history details out of the source. Update the affected comment block
near the byteLength handling in JSBuffer.cpp, and apply the same condensation to
the other listed comment blocks so they remain concise while preserving the key
behavior note.

Source: Coding guidelines


1560-1571: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Avoid uint16_t spans over Buffer bytes.

Both UCS2 helpers cast typedVector() to const uint16_t*; that assumes 2-byte alignment for arbitrary Buffer storage and is UB on targets that require aligned access. Keep the even-byte search rule, but compare bytes or realign before forming the span. Same fix applies to indexOf16.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/jsc/bindings/JSBuffer.cpp` around lines 1560 - 1571, The UCS2 search
helpers in JSBuffer, including lastIndexOf16 and indexOf16, are creating
std::span<const uint16_t> views directly from Buffer bytes, which can violate
alignment on some targets. Update these helpers to preserve the even-byte search
behavior without casting typedVector()/byte storage to uint16_t pointers;
instead compare as bytes or copy/realign before forming any 16-bit view so the
search works safely for arbitrary Buffer memory.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/jsc/bindings/JSBuffer.cpp`:
- Around line 1320-1333: The comment in JSBuffer-related code is too long and
exceeds the 3-line limit; trim the expanded explanation down to the durable
invariant only and move the Node-trace/history details out of the source. Update
the affected comment block near the byteLength handling in JSBuffer.cpp, and
apply the same condensation to the other listed comment blocks so they remain
concise while preserving the key behavior note.
- Around line 1560-1571: The UCS2 search helpers in JSBuffer, including
lastIndexOf16 and indexOf16, are creating std::span<const uint16_t> views
directly from Buffer bytes, which can violate alignment on some targets. Update
these helpers to preserve the even-byte search behavior without casting
typedVector()/byte storage to uint16_t pointers; instead compare as bytes or
copy/realign before forming any 16-bit view so the search works safely for
arbitrary Buffer memory.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fedbf851-9f12-4158-a98b-f9e99b95a6b9

📥 Commits

Reviewing files that changed from the base of the PR and between 8d02ea4 and 9415918.

📒 Files selected for processing (3)
  • src/jsc/bindings/JSBuffer.cpp
  • src/runtime/webcore/encoding.rs
  • test/js/node/buffer.test.js
💤 Files with no reviewable changes (1)
  • src/runtime/webcore/encoding.rs

@robobun

robobun commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator Author

Responding to CodeRabbit's two "outside diff range" findings (these did not create inline threads, so replying here).

Comment length. The 4-line comment I added at the indexOf dispatcher is trimmed to 3 in f71b5bd. The other flagged blocks (the limit capture and the encoding-ordering comment in fillBody) predate this PR and document detach-safety invariants I only changed one word in; I have left them alone rather than rewrite someone else's comments inside a bug-fix PR.

uint16_t spans over Buffer bytes (lastIndexOf16). Declining this one here, deliberately:

  • lastIndexOf16 is an exact mirror of the pre-existing indexOf16 a few lines above it, which has done the same (const uint16_t*) cast on the same possibly-unaligned buffer for a long time. Making only the new backward helper alignment-safe would leave the pair inconsistent, and changing both is a behavior-neutral refactor of an established pattern, which is a separate change from this compatibility fix.
  • It is also what the reference implementation does: Node's node_buffer.cc IndexOfString/IndexOfBuffer reinterpret_cast<const uint16_t*>(haystack) over the same Buffer memory (which can be an odd-offset subarray there too) and pass it to SearchString<uint16_t>.
  • Every target Bun ships (x86-64 and aarch64) supports unaligned 2-byte loads, and Bun's sanitizer lanes run ASan, which does not include UBSan's alignment check, so nothing observes this today.

Happy to make both helpers alignment-safe (e.g. memcpy each code unit, or a stride-2 byte comparison) as a follow-up if a maintainer wants it; I did not want to bury that inside this PR.

Comment thread src/bun_core/lib.rs Resolved
count16 and count16_z still used simdutf's non-validating UTF-16 length,
and their comments claimed it over-estimates for WTF-16. The opposite is
true: it charges a lone surrogate 2 bytes, while append16's fallback
(to_utf8_alloc) writes U+FFFD, 3 bytes. So the count -> allocate ->
append16 contract under-reserved by 1 byte per unpaired surrogate and
append16's bounds check returned None (not memory-unsafe, just wrong).

Route both through element_length_utf16_into_utf8, which this PR already
corrected, and fix the two comments.

Requested by @Jarred-Sumner in review.
Comment thread src/simdutf_sys/bun-simdutf.cpp Resolved
Comment thread src/jsc/bindings/JSBuffer.cpp Resolved
…ffer

The `byteLength < 8` check ran before the offset was examined at all,
but Node's checkBounds validates the offset's type first, and its
boundsError tests integer-ness before `length < 0`. So on a too-short
buffer Bun reported ERR_BUFFER_OUT_OF_BOUNDS for a non-number or
fractional offset where Node reports ERR_INVALID_ARG_TYPE (a TypeError)
or ERR_OUT_OF_RANGE "an integer".

Validate the offset fully first, then the buffer length, then the
range. This also merges the int32 and double paths (the separate int32
range check was duplicated) and keeps the too-short buffer ahead of the
range message, which Node also does.
@robobun

robobun commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator Author

CI status for whoever reviews this: on build 65193 278 of 280 jobs pass and there are zero test-failure annotations on the whole build. The only failures are two shards of the darwin 26 aarch64 - test-bun lane, which exit before running a single test:

Error: buildkite-agent artifact download timed out after 120s for step 'darwin-aarch64-build-bun'.
Refusing to continue with a partial download (would silently fall back to the wrong binary).

That identical 120-second artifact-download timeout has hit that one lane on every build of both of my open PRs today (65110, 65116, 65130, 65147, 65158, 65193), always before any test executes, so it is a macOS CI infrastructure issue and unrelated to this change. The darwin-aarch64-build-bun build job itself passes on every run; it is the transfer of its artifact to the test agents that times out.

The lanes this change actually affects are all green, including the debian 13 x64-asan test-bun shards that my earlier ErrorCode.ts index regression broke (and which this PR's 497ae87 fixed).

@Jarred-Sumner
Jarred-Sumner merged commit e384998 into main Jun 26, 2026
73 of 76 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the farm/03ab414d/node-buffer-divergences branch June 26, 2026 23:35
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.

2 participants