Skip to content

bundler: resolve require('bindings')(name) to the package's .node addon at build time - #36713

Open
robobun wants to merge 305 commits into
mainfrom
farm/a434bba6/bundler-resolve-bindings-native-addon
Open

robobun wants to merge 305 commits into
mainfrom
farm/a434bba6/bundler-resolve-bindings-native-addon

Conversation

@robobun

@robobun robobun commented Aug 1, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • Packages such as sqlite3, better-sqlite3, serialport or zeromq load their native addon with require("bindings")("name"). The bindings package locates the .node file at runtime by walking up from the caller's __filename to the package root and probing build/Release/name.node and a few sibling directories. Once the caller is bundled that walk starts at the bundle, and in a bun build --compile executable it fails immediately (bun --compile error. error: Could not find module root given file. #10964, Bun build does not work with the sqlite3 library #14301, better-sqlite3 .node exe is not bundled #8895):

    error: Could not find module root given file: "/$bunfs/root/app". Do you have a `package.json` file?
          at getRoot (/$bunfs/root/app:154:15)
          at bindings (/$bunfs/root/app:84:41)
    
  • Even when the walk could succeed, the .node file is never part of the build, because the bundler only ever sees require("bindings"), not the addon.

Fix

  • Parser (src/js_parser/p.rs, maybe_rewrite_bindings_require): when Features::rewrite_bindings_require is set and a call has the shape require("bindings")("name") or require("bindings")({ bindings: "name" }), the import record that require("bindings") produced is retargeted at name.node (the extension is added like bindings does) and tagged NativeBindings, and the call is replaced by a require of that record. Any other argument (further options, a computed name) changes what bindings would look for and is left alone.
  • The existing record is retargeted rather than a new one added next to it. A record merely marked unused is not dead: when the module containing it is reached through require() or import(), the barrel optimization un-defers every unused record of that module and resolves it, so the bindings package ended up bundled after all (the earlier revision of this PR, and bundler: resolve require('bindings')(name) to the built .node file #35642 and bundler: resolve require("bindings")(name) to a static .node import #35688, had this gap; their tests used a module.exports = require(...) module that the bundler collapses before linking, which hid it).
  • ParseTask enables the feature only for server-side targets, outside the dev server, and when bindings is not external (Resolver::is_package_external, which covers --external bindings and --packages external). Browser builds, dev-server builds and builds that externalize bindings are byte-for-byte what they were; the last one keeps the runtime lookup the user asked for.
  • Resolution (src/bundler/bundle_v2.rs, resolve_import_records): a NativeBindings record is looked up with Resolver::resolve_bindings_addon before anything else. The module root is found the way bindings' getRoot() finds it (nearest directory with a package.json, named or not, or a node_modules directory) and the build directories are probed in bindings 1.5.0's order, minus the two entries whose names encode the build host's Node version. On success the record now holds the addon's absolute path and continues through normal resolution, so plugins, --outdir and --compile treat it exactly like a hand-written require("/abs/addon.node") and the existing napi loader copies or embeds the file. On failure the record goes through the same path as any unresolvable import: a build error naming the addon; inside a try block, a require that throws at runtime, so packages with a JS fallback behave as they would with bindings; under ignoreModuleResolutionErrors, the same stub every other unresolvable require gets.
  • Why this is correct: the rewrite only replaces the call when the argument pins the addon name statically, which is exactly the input bindings would use at runtime, and it reproduces bindings' own search; everything bindings would do differently (other options, a dynamic name) is left to run at runtime. The missing-addon behaviour mirrors what bindings does at runtime (throw), moved to build time where it can be.
  • Verified with test/bundler/bundler_loader.test.ts (describe("require('bindings')"), 16 cases: both targets, both argument forms, other options and computed names left alone, a build/Debug addon with bindings not installed at all, both module-root rules, an addon named zlib, the missing-addon error, the try/catch fallback at runtime, external: ["bindings"], packages: "external", the browser target and the dev-server format; 10 fail on the released binary, the 6 left-alone cases pass on both and pin the gating) and test/napi/napi.test.ts, which compiles an app whose dependency loads the real napitests.node through require('bindings') and runs the executable from an unrelated directory (esm and cjs). bundler_loader, bundler_edgecase, bundler_cjs, bundler_barrel, bundler_npm and esbuild/packagejson pass.

Consolidation

#35642 and #35688 were alternative fixes for the same issue. Folded in from #35642: appending .node in the parser, not touching browser builds, the build/Debug case without bindings installed, and the docs note; from #35688: the { bindings: "name" } form and the negative test for other options. Not adopted: #35642 left a missing addon as a runtime require("name.node") and #35688 turned it into an empty stub; both hid the problem until runtime, and #35688's stub also defeated try/catch fallbacks. The bindings package itself is not vendored as a fixture (a throwing stub proves the same thing).

Background

  • Import record: the bundler's per-module list of things a module imports or requires; the parser creates one per require("x") and the bundler resolves them after parsing. Expressions refer to records by index.
  • HANDLES_IMPORT_ERRORS / WAS_UNRESOLVED: record flags meaning "this require is inside a try" and "resolution failed"; the printer turns a record with both into a throwing expression and one with only the latter into an empty stub, which is how unresolvable requires behave everywhere else in the bundler.
  • Barrel optimization: re-export-only modules have their unneeded import records marked unused and resolved later on demand; a require() or import() of a module requests all of it, which un-defers every unused record in it.
  • Napi loader: the bundler's handling of .node files, which copies the file next to the bundle or embeds it into a compiled executable and rewrites the require to point at it.

Fixes #10964
Fixes #14301
Fixes #8895


no test proof · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/bundler/bundler_compile.test.ts test/napi/napi.test.ts

@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The compiler now recognizes literal require("bindings")("<name>") calls, resolves matching native addons, embeds .node assets, and reports or suppresses resolution failures according to context. Tests cover bundling, externalization, fallback behavior, non-literal calls, and N-API execution.

Changes

Native bindings support

Layer / File(s) Summary
Parse bindings calls
src/ast/import_record.rs, src/js_parser/visit/visit_expr.rs
Adds the NativeBindings import tag and transforms literal require("bindings") calls into tagged import records.
Resolve native addons
src/bundler/bundle_v2.rs, src/resolver/resolver.rs
Searches standard package and build paths for .node files, bypasses generic builtin and plugin resolution, and handles missing or external native addons.
Validate bindings support
test/bundler/bundler_compile.test.ts, test/napi/napi.test.ts
Tests embedded addons, missing addons, builtin-name handling, externalization, try/catch fallback, non-literal calls, and execution from another directory.

Suggested reviewers: jarred-sumner, dylan-conway

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly and concisely describes the main change: build-time resolution of require('bindings')(name) to a native addon.
Description check ✅ Passed The description explains the problem, fix, behavior, scope, verification, and related issues, although it does not use the template headings exactly.

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

@github-actions github-actions Bot added the claude label Aug 1, 2026
@robobun

robobun commented Aug 1, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 1:05 AM PT - Aug 13th, 2026

❌ @robobun, your commit 5fee0de has 1 failures in Build #94182 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 36713

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

bun-36713 --bun

Comment thread src/ast/import_record.rs Outdated
Comment thread src/bundler/bundle_v2.rs Outdated
Comment thread src/js_parser/visit/visit_expr.rs Outdated
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Found 3 issues this PR may fix:

  1. bun --compile error. error: Could not find module root given file. #10964 - The bindings package fails at runtime inside a --compile binary with "Could not find module root given file"; build-time resolution eliminates this lookup entirely
  2. better-sqlite3 .node exe is not bundled #8895 - better-sqlite3 uses require('bindings') to find its .node addon, which the bundler currently cannot resolve
  3. Auto-install doesn't work for sqlite3 library #9292 - sqlite3 uses require('bindings')('node_sqlite3') which fails to locate the .node file; build-time resolution fixes this

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #10964
Fixes #8895
Fixes #9292

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. bundler: resolve require('bindings')(name) to the built .node file #35642 - Also resolves require('bindings')(name) to the built .node file at bundle time using the same directory-probing approach
  2. bundler: resolve require("bindings")(name) to a static .node import #35688 - Also rewrites require("bindings")(name) to a static .node import at build time via import record retagging

🤖 Generated with Claude Code

Comment thread src/ast/import_record.rs Outdated
Comment thread src/bundler/bundle_v2.rs Outdated
Comment thread src/js_parser/visit/visit_expr.rs 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.

Additional findings (outside current diff — PR may have been updated during review):

  • 🔴 src/bundler/bundle_v2.rs:6062-6091 — The error's suggested remedy "mark the import external" is unactionable: neither --external bindings nor --external <addon> can suppress this error, because the parser already marked the bindings record IS_UNUSED (skipped at bundle_v2.rs:5884) and the NativeBindings not-found branch does continue 'outer before ever reaching resolve_with_framework (the only place external patterns are consulted). This is also a behavior regression — a project that today passes --external bindings to defer addon loading to runtime will now fail to build when the .node file isn't present on the build machine. Consider checking the resolver's external patterns for bindings / the addon name before emitting the fatal error, and reword the hint to name the working escape (--external <package-that-calls-bindings>, e.g. --external sqlite3).

    Extended reasoning...

    What the bug is

    The new error message reads:

    Could not locate native addon "…" for the "bindings" package. Run "bun install" to build it, or mark the import external.

    But there is no --external value the user can pass that suppresses this error for the import in question. The remedy is unactionable as written, and the rewrite additionally regresses the previously-working --external bindings escape hatch.

    Code path that triggers it

    Step 1 — parser rewrite is unconditional on --external. In src/js_parser/visit/visit_expr.rs:2015-2064, the rewrite fires whenever p.options.bundle is set and the target is ERequireString with path.text == b"bindings". It then:

    • marks the original bindings import record IS_UNUSED (line ~2032-2034), and
    • creates a new NativeBindings-tagged record whose path.text is the addon name.

    The parser has no access to the resolver's external-pattern list, so --external bindings cannot influence this step.

    Step 2 — --external bindings never reaches the resolver. In bundle_v2.rs:5882-5891, records with IS_UNUSED are skipped before any resolution runs. The bindings record therefore never reaches resolver.resolve_with_framework (line 6169), which is where is_external_pattern is consulted (resolver.rs:1209). So passing --external bindings is a no-op for the immediately-called form.

    Step 3 — --external <addon_name> never reaches the resolver either. The NativeBindings branch at bundle_v2.rs:6047 runs before resolve_with_framework. In the None arm it sets path.is_disabled = true, logs the error, and does continue 'outer at line 6091 — the resolver is never called for this record, so is_external_pattern(addon_name) is never consulted.

    There is no external-pattern check anywhere between line 5884 and line 6047; only builtin/bun:*/onResolve-plugin handling sits in between.

    Concrete walkthrough

    Given node_modules/mypkg/index.js containing module.exports = require('bindings')('myaddon') with no myaddon.node on disk, and the user runs:

    1. Parser visits the call, sees bundle=true, matches ERequireString("bindings"), marks record #N IS_UNUSED, creates record #M tagged NativeBindings with path.text = "myaddon".
    2. Resolve loop iteration for record #N: IS_UNUSED → continue at 5890. --external bindings never checked.
    3. Resolve loop iteration for record #M: enters NativeBindings block at 6047, resolve_native_bindings returns None, HANDLES_IMPORT_ERRORS is unset → error logged → continue 'outer at 6091. --external myaddon never checked.
    4. Build fails with "…or mark the import external", but the user already did.

    Trying --external myaddon or --external myaddon.node produces the identical failure for the same reason (step 3 short-circuits before the resolver).

    The only working escape is --external mypkg — externalizing the enclosing package so mypkg/index.js is never parsed at all — which the message does not say and which a user reading "mark the import external" would not naturally infer (the import in question is bindings, not mypkg).

    Why this matters

    • REVIEW.md is explicit: "Error messages are reviewed word-for-word as code … a concrete remedy." A remedy that cannot work as stated blocks merge.
    • Behavior regression: before this PR, bun build --outdir dist --external bindings ./entry.ts would emit require('bindings')('myaddon') verbatim, and the bundle would work at runtime provided node_modules is deployed alongside it. After this PR, the same command fails the build when the .node file isn't present on the build machine (common when bundling in CI/Docker without native toolchains and building the addon on the target). The flag the user already relies on is now silently ignored for this shape.

    How to fix

    Two independent pieces:

    1. Make the escape hatch work. In the None arm of the NativeBindings block (bundle_v2.rs:6062-6091), before emitting the error, consult transpiler.resolver.is_external_pattern(b"bindings") (and/or is_external_pattern(import_record.path.text)). If either matches, skip the error — either leave the record disabled (so the call becomes a no-op like a try/catch fallthrough) or, better, restore the original require('bindings')(name) shape as an external call so runtime resolution still works.
    2. Reword the remedy to name the escape that actually works today: e.g. "…or add --external <package> for the package that loads it." — pointing at the enclosing package (sqlite3, mypkg) rather than at bindings or the addon.

Comment thread src/bundler/bundle_v2.rs Outdated
Comment thread test/bundler/bundler_compile.test.ts Outdated
Comment thread src/bundler/bundle_v2.rs Outdated
Comment thread test/bundler/bundler_compile.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.

No further findings from me — the earlier three rounds of feedback (builtin-alias/onResolve interception, --external bindings regression, vacuous test assertion) are all addressed and now covered by tests. Deferring to a human because this is a user-facing bundler feature with design choices (hard-fail on missing .node, which bindings search prefixes to probe, --external semantics) and two open sibling PRs (#35642, #35688) that a maintainer should reconcile.

What was reviewed: the Tag::NativeBindings insertion vs. the ordinal is_internal() check (safe — placed before Runtime, threshold is symbolic); that the rewrite only fires under p.options.bundle with a string-literal arg so transpile-only and non-literal callers are untouched; that the not-found path sets is_disabled and honours HANDLES_IMPORT_ERRORS / ignore_module_resolution_errors / --external bindings; and that the new napi --compile test runs the binary from an empty cwd so it can't accidentally find the source addon.

Extended reasoning...

Overview

The PR adds build-time resolution of require('bindings')('<name>') to the actual .node addon so bun build --compile can embed native modules. It touches four native source files: a new Tag::NativeBindings enum variant in src/ast/import_record.rs; a parser rewrite in src/js_parser/visit/visit_expr.rs that marks the bindings import record IS_UNUSED and emits a tagged require of the addon name; resolve_native_bindings + tag-specific handling in src/bundler/bundle_v2.rs that probes the enclosing package's build/Release-style directories and either rewrites to an absolute .node path or fails the build; and a pub(crate) → pub visibility widening on Resolver::is_external_pattern. Six new itBundled cases and one end-to-end napi --compile test cover the happy path, not-found error, try/catch fall-through, builtin-name collision, --external escape, and non-literal passthrough.

Security risks

None identified. The filesystem probing (bun_sys::exists over a fixed prefix list joined under the enclosing package.json's directory) runs at build time on the developer's machine, not on untrusted input; the addon name is a literal from source the user is already bundling.

Level of scrutiny

High. This is a new user-facing bundler behaviour with a build-failure mode that did not exist before: a project whose .node addon is not yet built (fresh clone, cross-compile host, CI without native deps) now fails bun build unless the call is in try/catch or bindings is externalised. The PR intentionally omits two of the real bindings package's search paths (the ABI-versioned ones) and does not handle the object-argument form require('bindings')({ bindings: 'name' }). Those are reasonable scoping decisions but a maintainer should sign off on them, and on which of the three competing PRs (#35642, #35688, this one) to land.

Other factors

I reviewed this PR three times previously; each finding (builtin-alias / onResolve plugin intercepting the addon name, --external bindings no longer suppressing the error and the error text naming an unreachable remedy, a vacuous .not.toContain assertion, and the misplaced #8697 issue comment) was addressed in commits ae43811, 83ebb0e, and 1757a9f, with new tests compile/NativeBindingsBuiltinNameDoesNotAlias and compile/NativeBindingsExternalSkipsError covering the fixes. This run's bug hunt found nothing new. I spot-checked that inserting NativeBindings mid-enum does not break Tag::is_internal() (it compares against Tag::Runtime as u8 symbolically, and the new variant sits below that threshold). Test coverage looks solid for the shapes handled; the CI failures in the robobun comment (36577.test.ts, bun-upgrade.test.ts) are unrelated to this change.

@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: 3

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

Inline comments:
In `@src/js_parser/visit/visit_expr.rs`:
- Around line 2010-2055: Update the bindings handling in visit_expr.rs around
the require-expression transformation so the original runtime
require("bindings") call remains available when the derived native addon record
cannot be resolved, rather than marking it unused unconditionally; preserve or
emit an external fallback for that case. In src/bundler/bundle_v2.rs around
lines 6045-6100, add a runtime regression test covering an external bindings
package with a missing addon and verify the generated code still performs the
runtime lookup.

In `@test/bundler/bundler_compile.test.ts`:
- Around line 393-408: Strengthen the itBundled case
“compile/NativeBindingsTryCatchFallsThrough” by inspecting the generated bundle
and asserting it excludes the native-binding failure text “Could not find module
root given file” and the getRoot symbol. Keep the existing fallback runtime
assertion, ensuring the test also detects when the bindings call is not
suppressed and rewritten.

In `@test/napi/napi.test.ts`:
- Around line 207-217: Update the N-API executable test around spawnSync to
create a dedicated fixture temporary directory, set BUN_TMPDIR to that directory
in the child environment, and assert readdirSync on it is empty after execution.
Keep the existing output and success assertions, and do not rely on cwd/runDir
to verify embedded native-addon usage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e265731f-ee6f-4d77-b62a-5716109a6a52

📥 Commits

Reviewing files that changed from the base of the PR and between f91d5c9 and 600f61c.

📒 Files selected for processing (6)
  • src/ast/import_record.rs
  • src/bundler/bundle_v2.rs
  • src/js_parser/visit/visit_expr.rs
  • src/resolver/resolver.rs
  • test/bundler/bundler_compile.test.ts
  • test/napi/napi.test.ts

Comment thread src/js_parser/visit/visit_expr.rs Outdated
Comment thread test/bundler/bundler_compile.test.ts Outdated
Comment thread test/napi/napi.test.ts

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

No new findings this pass — the earlier three rounds of feedback (builtin-alias/onResolve interception, the --external escape hatch and error wording, and the vacuous ExternalSkipsError assertion) have all been addressed. Deferring to a maintainer: this is a user-facing bundler feature with design choices (probe-path set, hard-fail on missing addon, --external bindings semantics) and two open PRs (#35642, #35688) covering the same ground.

What was reviewed:

  • Tag::NativeBindings insertion vs. Tag::is_internal() ordinal check — placed before Runtime, so the >= Runtime comparison is preserved and no other code keys on the discriminant value.
  • Parser rewrite in visit_expr.rs: the bindings record is marked IS_UNUSED only after add_import_record_by_range reborrows p.import_records, and import_records_for_current_part is updated so the new record is linked.
  • resolve_native_bindings: uses bun_sys::exists + pooled path buffer + enclosing_package_json walk; the resolved path is arena-interned before assignment.
Extended reasoning...

Overview

The PR adds build-time resolution of require('bindings')('<name>') so that bun build --compile can embed the referenced .node addon instead of shipping a binary that fails at runtime with "Could not find module root". It touches four source files: a new Tag::NativeBindings enum variant in src/ast/import_record.rs; a parser rewrite in src/js_parser/visit/visit_expr.rs that detects the call shape, marks the bindings import unused, and emits a tagged require record for the addon name; a filesystem probe + record rewrite in src/bundler/bundle_v2.rs::resolve_import_records (with a new resolve_native_bindings helper); and a pub(crate) → pub visibility widening on Resolver::is_external_pattern. Six new itBundled cases in test/bundler/bundler_compile.test.ts and one end-to-end --compile test in test/napi/napi.test.ts cover the feature.

Security risks

None identified. The probe reads only from directories under the enclosing package.json of the importing file at build time (same trust boundary as the resolver already operates in), and the addon name comes from a source-code string literal, not runtime input. No path components are user-controlled beyond what the bundler already resolves.

Level of scrutiny

High. This is a feature change to the bundler's import-record resolution loop and the parser's visit pass — both hot, correctness-critical paths that affect every bundle. It changes user-visible behaviour: builds that previously succeeded (and produced a broken-at-runtime bundle) now hard-fail when the .node file is absent and the call is not in a try/catch. The chosen probe-directory list, the decision to skip the ABI-versioned compiled/ and lib/binding/ paths, and the --external bindings escape-hatch semantics are all design calls a maintainer should sign off on. There are also two open PRs (#35642, #35688) that github-actions flagged as covering the same ground with the same approach.

Other factors

I've already run three review passes on this PR; each finding was addressed with a follow-up commit and a covering test (NativeBindingsBuiltinNameDoesNotAlias, NativeBindingsExternalSkipsError, and the tightened assertions in 1757a9f). This pass I additionally checked that inserting NativeBindings before Runtime in the #[repr(u8)] enum does not break Tag::is_internal() (it compares >= Tag::Runtime as u8, and grep shows no other ordinal use of this enum), and that the parser reborrows p.import_records after add_import_record_by_range may have grown it, so the earlier record borrow does not dangle. The two CI failures reported by robobun (36577.test.ts on Windows x64 and bun-upgrade.test.ts on Windows aarch64) are unrelated to the changed files. Given the scope and the duplicate-PR situation, this needs a human maintainer's decision rather than a bot approval.

@robobun

robobun commented Aug 1, 2026 •

Copy link
Copy Markdown
Collaborator Author

CI status for the current revision (5fee0de, build #94182): every build lane and 155 of 156 completed test shards are green, including the bundler and napi suites this PR touches (bundler_loader.test.ts require('bindings') cases and the napi.test.ts --compile cases passed on every lane that ran them). The one red shard is test/js/web/timers/setInterval.test.js on x64-asan, which is failing on main independently of this change and has been reported separately. The remaining entries in the annotation (napi_get_value_string output ordering, child_process, spawn-maxbuf, and a few others) passed on retry or when run alone. Four Windows shards expired without getting an agent (the same build-queue issue seen on earlier builds); the Windows shards that did run passed.

Earlier revision (600f61c, before the rework): build #87502

All build lanes green; all Linux test lanes (incl. ASAN), macOS, and the Windows shards that got an agent were green. Remaining red was Azure VM-creation throttling, the pre-existing bun-upgrade.test.ts win-aarch64 failure, and flaky tests that passed alone or on retry. Builds #87266 and #87342 before it expired without running anything (build-queue backlog).

robobun and others added 11 commits August 3, 2026 00:52
… and resolver/fs (#36803)

Net: +47 / -1160.

## Removed

- **`src/runtime/node/nodejs_error_code.rs`** (1113 lines): a
340-variant `enum Code` mirroring the Node.js `ERR_*` table. The sole
reference outside its own module was `node_os.rs:269` doing `<&'static
str>::from(ErrorCode::ERR_SYSTEM_ERROR)`, which just produces the string
`"ERR_SYSTEM_ERROR"`. Three other call sites in the same file
(`node_os.rs:938/1220/1539`) already use
`BunString::static_("ERR_SYSTEM_ERROR")` directly, so the remaining one
now does the same. The `jsc::ErrorCode` type (backed by
`ErrorCode.generated.rs`) is the live `ERR_*` table; this enum was a
parallel dead one.
- `rg -n 'nodejs_error_code' src/ build/debug/codegen/ src/codegen/` →
only the `mod` declaration and two explanatory comments (both updated).
- **`dir_iterator::IteratorError`** (11 lines) +
**`runtime::Error::DirIterator`** variant (3 lines): the enum is never
constructed (`rg 'IteratorError::' src/ build/debug/codegen/` → 0 hits),
so the `#[from]` on `Error::DirIterator` can never fire either.
- **`VectorArrayBuffer::to_js`** (4 lines): every caller reads `.value`
directly; `to_js` was never invoked and is not a trait impl.
- **`src/resolver/fs.rs`** commented-out Zig stubs
`statBatch/stat/readFile/readDir` (9 lines): never implemented.

## Verification

```
rg -w <symbol> src/ build/debug/codegen/ src/codegen/
bun bd
bun run rust:check-all   # 10 ok, 0 failed
bun bd test test/js/node/os/ test/js/node/fs/fs.test.ts -t readdir
bun bd test test/bundler/bundler_loader.test.ts
bun bd test test/internal/source-lints/
```

A source-lint test
(`test/internal/source-lints/dead-symbols-nodejs-error-code.test.ts`)
asserts none of these reappear.

## Also scanned (nothing removed)

`src/http/**` (36 files), `src/install/**`, `src/resolver/**`,
`src/ast/**`, `src/semver/**`, `src/valkey/**`, `src/sql/postgres/**`,
`src/collections/**`. Several initial candidates turned out to have
callers under a different crate or via method-call syntax:
`collections::StringMap` (sql_jsc), `semver::string::ArrayHashContext`
(install/lockfile), `NewWriter::{int8,f64,bun_string}` (sql_jsc),
`Level::{gt,eql}` (js_parser), `SinglyLinkedList::len`
(bake/memory_cost), `Target::is_node` (resolve_builtins),
`{Parse,Decode}DataURLError::name()` (bundler/transpiler).

No overlap with open dead-code PRs #36237, #35775, #36791, #36115,
#35437, #35880.

<!-- robobun:evidence:begin -->

---

**[review]** gate passed · iteration 1 · 10 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 1 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/dead-symbols-nodejs-error-code.test.ts
bun test v1.4.0 (6071f67)

test/internal/source-lints/dead-symbols-nodejs-error-code.test.ts:
24 |     ["src/runtime/error.rs", /\bDirIterator\b/],
25 |     ["src/runtime/node/types.rs", /impl VectorArrayBuffer \{\n    pub fn to_js\(/],
26 |     ["src/resolver/fs.rs", /pub fn statBatch\(fs: \*FileSystemEntry/],
27 |   ];
28 |   const resurrected = checks.filter(([file, re]) => re.test(src(file))).map(([file, re]) => `${file}: ${re.source}`);
29 |   expect(resurrected).toEqual([]);
                           ^
error: expect(received).toEqual(expected)

- []
+ [
+   "src/runtime/node.rs: \bnodejs_error_code\b",
+   "src/runtime/node/node_os.rs: crate::node::ErrorCode",
+   "src/runtime/node/dir_iterator.rs: \benum IteratorError\b",
+   "src/runtime/error.rs: \bDirIterator\b",
+   "src/runtime/node/types.rs: impl VectorArrayBuffer \{\n    pub fn to_js\(",
+   "src/resolver/fs.rs: pub fn statBatch\(fs: \*FileSystemEntry",
+ ]

- Expected  - 1
+ Received  + 8

      at <ano
... (truncated)

release without fix: 1 FAILED
bun test v1.4.0-canary.1 (a6ff9d1)

test/internal/source-lints/dead-symbols-nodejs-error-code.test.ts:
24 |     ["src/runtime/error.rs", /\bDirIterator\b/],
25 |     ["src/runtime/node/types.rs", /impl VectorArrayBuffer \{\n    pub fn to_js\(/],
26 |     ["src/resolver/fs.rs", /pub fn statBatch\(fs: \*FileSystemEntry/],
27 |   ];
28 |   const resurrected = checks.filter(([file, re]) => re.test(src(file))).map(([file, re]) => `${file}: ${re.source}`);
29 |   expect(resurrected).toEqual([]);
                           ^
error: expect(received).toEqual(expected)

- []
+ [
+   "src/runtime/node.rs: \bnodejs_error_code\b",
+   "src/runtime/node/node_os.rs: crate::node::ErrorCode",
+   "src/runtime/node/dir_iterator.rs: \benum IteratorError\b",
+   "src/runtime/error.rs: \bDirIterator\b",
+   "src/runtime/node/types.rs: impl VectorArrayBuffer \{\n    pub fn to_js\(",
+   "src/resolver/fs.rs: pub fn statBatch\(fs: \*FileSystemEntry",
+ ]

- Expected  - 1
+ Received  + 8

      at <anonymous> (/workspace/bun/test/internal/source-lints/dead-symbols-nodejs-error-code.test.ts:29:23)
(fail) dead Rust symbols in runtime/node + resolver do not reappear [0.74ms]

 0 pass
 1 fail
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/dead-symbols-nodejs-error-code.test.ts
bun test v1.4.0 (6071f67)

test/internal/source-lints/dead-symbols-nodejs-error-code.test.ts:
(pass) dead Rust symbols in runtime/node + resolver do not reappear [30.31ms]

 1 pass
 0 fail
 1 expect() calls
Ran 1 test across 1 file. [2.02s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 652ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/73] gen ErrorCode+*.h
[2/11] gen cpp.rs (cppbind)
[3/11] gen BunProcess.lut.h
Generating /workspace/bun/build/release/codegen/BunProcess.lut.h from /workspace/bun/src/jsc/bindings/BunProcess.cpp
[4/11] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 238 extern-C blocks audited
[4/11] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_resolver v0.0.0 (/workspace/bun/src/resolver)
�[1m�[92m   Compiling�[0m bun_router v0.0.0 (/workspace/bun/src/router)
�[1m�[92m   Compiling�[0m bun_bundler v0.0.0 (/workspace/bun/src/bundler)
�[1m�[92m   Compiling�[0m bun_standalone_graph v0.0.0 (/workspace/bun/src/standalone_graph)
�[1m�[92m   Compiling�[0m bun_transpiler v0.0.0 (/workspace/bun/src/transpiler)
�[1m�[92m   Compiling�[0m bun_bunfig v0.0.0 (/workspace/bun/src/bunfig)
�[1m�[92m   Compiling�[0m bun_instal
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
src/jsc/ErrorCode.rs                               |   14 +-
 src/jsc/lib.rs                                     |    4 +-
 src/resolver/fs.rs                                 |    9 -
 src/runtime/error.rs                               |    3 -
 src/runtime/node.rs                                |    4 -
 src/runtime/node/dir_iterator.rs                   |   11 -
 src/runtime/node/node_os.rs                        |    4 +-
 src/runtime/node/nodejs_error_code.rs              | 1113 --------------------
 src/runtime/node/types.rs                          |    4 -
 .../dead-symbols-nodejs-error-code.test.ts         |   30 +
 10 files changed, 35 insertions(+), 1161 deletions(-)
```

</details>

**gate history** · 1 passed · 1 rejected · iteration 1

<details><summary>evidence per changed file</summary>

```
file                                                      reads  edits  tests
src/jsc/ErrorCode.rs                                          3      3      0
src/jsc/lib.rs                                                1      1      0
src/resolver/fs.rs                                            1      1      0
src/runtime/error.rs                                          1      1      0
src/runtime/node.rs                                           1      1      0
src/runtime/node/dir_iterator.rs                              1      1      0
src/runtime/node/node_os.rs                                   1      1      0
src/runtime/node/nodejs_error_code.rs                         0      0      0
src/runtime/node/types.rs                                     1      1      0
…nal/source-lints/dead-symbols-nodejs-error-code.test.ts      2      4      0
```

</details>

<!-- robobun:evidence:end -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…n't deref a freed NewSource box (#36799)

## What

`fetch(url, { signal })` + `resp.body.getReader()` + `reader.cancel()`,
drop the Response, then abort the signal: heap-use-after-free on the
response body stream's native `Box<NewSource<_>>`. Reproduces 5/5 under
ASAN on `main`, clean on `5b7c3cacbe63` (before #36624).

```
READ of size 1 in NewSource<ByteBlobLoader>::cancel (ReadableStream.rs:936)
  <- ReadableStream::done <- ReadableStream::error
  <- BodyAbortListener::on_abort (Response.rs:142)
  <- AbortSignal::runAbortSteps <- Timeout::run <- __bun_fire_timer
freed by: JSDestructibleObjectDestroyFunc <- PreciseAllocation::sweep
  <- sweepPreciseAllocations <- Heap::sweepInFinalize
allocated by: Box::new(NewSource<..>) <- Value::to_readable_stream <- Response.body getter
```

Minimal repro (crashes at iteration 0 on debug and release ASAN):

```js
const server = Bun.serve({ port: 0, fetch: () => new Response(Buffer.alloc(20000, "x")) });
async function one(signal) {
  const resp = await fetch(server.url, { signal });
  const rd = resp.body.getReader();
  await rd.read();
  await rd.cancel();
}
for (;;) {
  const ac = new AbortController();
  await Promise.all([one(ac.signal), one(ac.signal), one(ac.signal)]);
  Bun.gc(false);            // must NOT be gc(true)
  await Bun.sleep(5);
  ac.abort();
  await Bun.sleep(5);
}
```

## Cause

#36624 changed `readable_stream::Strong` so that `check_body_stream_ref`
downgrades `Body.Locked.readable` from a `bun_jsc::Strong` to a raw
`weak: JSValue` once the Response wrapper's traced `m_stream`
WriteBarrier slot owns the stream. That raw JSValue is never cleared
when the stream is collected.

After `reader.cancel()` (ByteStream path) or once the body is fully
buffered (ByteBlobLoader path), nothing but the Response wrapper's
`m_stream` roots the stream. When the user drops the Response, one eden
GC:

1. reaps weak handles (so a real `JSC::Weak` would already read `None`),
2. runs `sweepInFinalize` -> `sweepPreciseAllocations`, which sweeps the
`JS{Bytes,Blob}InternalReadableStreamSource` cell **synchronously**; its
destructor runs `NewSource::finalize` -> `decrement_count` ->
`drop(Box<NewSource<_>>)`,
3. leaves the `JSResponse` wrapper's MarkedBlock cell for lazy sweep.

`Response::finalize` (the wrapper cell destructor) is what sets `js_ref`
to `Finalized` and drops `abort_listener`, so in that window the native
`Response` is still alive, `Locked.readable` still holds the stream's
raw JSValue, and `BodyAbortListener` is still registered.
`AbortSignal.timeout` / `ac.abort()` then fires:

* `on_abort` -> `get_body_readable_stream` reaches the dead stream
through either `js_ref()` (raw JSValue to the dead-but-unswept wrapper)
or `Locked.readable`'s raw JSValue -> `ReadableStreamTag__tagged` ->
`m_nativePtr` -> the destructed source cell's `m_ctx` -> the freed
`Box<NewSource<_>>`, and
* `Value::to_error_instance` reads the same handle via
`strong_readable.get()` -> `bytes.on_data()`.

`Bun.gc(true)` does not reproduce: a full synchronous sweep runs the
wrapper destructor in the same pass, which drops the listener before
abort can fire.

## Fix

* `readable_stream::Strong::weak` is now `bun_jsc::Weak<()>` instead of
a raw `JSValue`. `downgrade()` creates it with the new
`WeakRefType::None` (no finalize owner; `JSC::Weak` accepts a null
owner). `get()` / `has()` / `is_disturbed()` / `tee()` all see `None`
once the stream is reaped, so `Value::to_error_instance` and every other
`Locked.readable` reader skip the freed `NewSource` deref.
* `BodyAbortListener::on_abort` reads the stream via `Locked.readable`
directly instead of `get_body_readable_stream`, whose `js_ref()` path
would still read a raw `JsRef::Weak(JSValue)` to the dead-but-unswept
wrapper. `Locked.readable.get()` returns the live stream when a reader
roots it without the wrapper, and `None` exactly when the stream (and
its `NewSource` box) is collected.
* `bun_jsc::Weak::create_passive` and a `WeakRefType::None` arm in
`Bun__WeakRef__new` support an ownerless `JSC::Weak`.

No user-visible behaviour changes: when the stream is collected
`readable.error()` was already a no-op (`webStreamControllerError`
early-returns on a non-Readable stream; `NewSource::cancel`
early-returns on `cancelled`); when the stream is alive
`readable.error()` runs exactly as before.

## Verification

* `test/js/web/fetch/fetch-abort-after-cancel-gc-fixture.ts` + test:
crashes at iteration 0 on `main` under debug+ASAN (`bun bd test`),
passes with this PR.
* release+ASAN: 5/5 clean on both the fixture (100 iters) and the
original fuzzer repro (300 iters); `main` crashes 5/5 and 4/4
respectively.
* `test/js/bun/http/serve-http3.test.ts` "client abort during streaming
response" passes 3/3 on release+ASAN (regressed on the first commit of
this PR; fixed in c5da841).
* #36624's leak regression test `test/regression/issue/29267` still
passes (the downgrade is preserved; only the storage is now a real
Weak).
*
`test/js/web/fetch/{fetch-abort-stream-body,body,body-stream,body-clone,fetch-abort-queued,fetch-stream-cancel-leak}.test.ts`
and
`test/js/web/streams/readable-stream-terminal-barrier-release.test.ts`
unchanged vs `main`.

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 1 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/js/web/fetch/fetch-abort-stream-body.test.ts

<!-- robobun:evidence:end -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
… InlineBlob, Ipc.ts (#36791)

Net -817 lines (+82 / -899) across 39 files. Every removed symbol was
verified via `rg` across `src/` and `build/debug/codegen/` to have zero
remaining references; `bun bd` and `bun run rust:check-all` (all CI
target triples) pass.

## Whole-file deletions (C++ headers)

- `src/jsc/bindings/TextCodecASCIIFastPath.h` (78 lines): never
`#include`d anywhere under `src/`, `scripts/`, `cmake/`, or generated
code. Defines `PAL::UCharByteFiller` / `PAL::copyASCIIMachineWord`,
neither referenced.
- `src/jsc/bindings/webcore/Node.h` (108 lines): defines `WebCore::Node`
which is never instantiated, subclassed, or referenced as a type. No
`WTF_MAKE_TZONE_ALLOCATED_IMPL(Node)` exists so it could never link.
Removed the two `#include "Node.h"` lines in `WebCoreOpaqueRoot.h` and
`EventTargetHeaders.h` (neither file uses the type).
- `src/jsc/bindings/webcore/JSDOMWindow.h`, `JSServiceWorker.h`,
`JSWindowProxy.h`: one-line stub headers. Removed their `#include` sites
in `JSMessageEvent.cpp`, `JSMessageEventCustom.cpp`, and the
commented-out includes in `JSEventTargetCustom.cpp`.

## Dead `extern "C"` exports (lost their Rust caller in #35002)

- `highway_strings.cpp`: `highway_char_frequency` +
`ScanCharFrequencyImpl` (69-line SIMD body) + `HWY_EXPORT`. Whole-repo
`rg -w highway_char_frequency` = 1 hit (its own definition).
- `TextCodecWrapper.cpp`: `Bun__isEncodingSupported`,
`Bun__getCanonicalEncodingName`. Whole-repo `rg` = 1 hit each.
- `StrongRef.cpp` / `.h`: `Bun__StrongRef__get`,
`Bun__StrongRef__clear`. `src/jsc/Strong.rs` only declares
`new`/`set`/`delete`; reads go through a direct pointer load per the
comment at `StrongRef.cpp:19-26`. Also removed
`StrongRootBlock::clearValue` which was only called from
`Bun__StrongRef__clear`.
- `InspectorLifecycleAgent.cpp`: `Bun__LifecycleAgentReportReload`
wrapper + `InspectorLifecycleAgent::reportReload()` method + header
decl. No Rust caller.
- `InspectorBunFrontendDevServerAgent.cpp`:
`InspectorBunFrontendDevServerAgent__notifyClientErrorReported` /
`notifyGraphUpdate` wrappers + `clientErrorReported()` / `graphUpdate()`
class methods + header decls. `inspector_agent.rs` declares the 7
sibling `notify*` wrappers but not these two.
- `InspectorBunFrontendDevServerAgent.h`: removed the stale 9-entry
`extern "C"` block that declared `BunFrontendDevServerAgent__notify*`
(no `Inspector` prefix), which never matched the actual
`InspectorBunFrontendDevServerAgent__notify*` definitions or the Rust
imports.
- `JSS3File.cpp`: `static bool customHasInstance(...)` is a file-scope
static function, never referenced in the file. `JSS3File`'s
`StructureFlags` is `Base::StructureFlags` with no
`ImplementsHasInstance`, so `CREATE_METHOD_TABLE` cannot pick it up.
Removed the matching `JSS3File__hasInstance` forward decl, the
`has_instance` helper in `S3File.rs`, and its `#[no_mangle]` export
whose only caller was this static.

## Commented-out C++ (>6 months stale per `git blame`)

- `JSTextEncoder.cpp`: disabled DOMJIT declarations, `DOMJIT::Signature`
statics, `HashTableValue` entries, and two `JSC_DEFINE_JIT_OPERATION`
bodies (83 lines total). Commented out since 2024-09; the non-DOMJIT
`encode`/`encodeInto` entries remain.
- `JSURLSearchParams.cpp`, `JSErrorEvent.cpp`, `JSDOMException.cpp`:
`#if ENABLE(BINDING_INTEGRITY)` vtable-pointer scaffolding inside and
before `toJSNewlyCreated`. Commented out since 2022-03.
- `JSPerformance.cpp`, `JSDOMURL.cpp`: `JSDOMWindowBase`-gated
`deleteProperty` paths in `finishCreation`/`initializeProperties`.
Commented out since 2024.
- `JSWorkerOptions.cpp`: `credentials`/`type` dictionary-member parsing.
Commented out since 2023-07 / 2025-01.
- `JSEventListener.cpp`: `handleBeforeUnloadEventReturnValue` helper and
its call site. Commented out since 2022-03.
- `PerformanceUserTiming.cpp`: `restrictedMarkFunctions` lookup inside
`convertMarkToTimestamp`. Commented out since 2024-01.

## Rust

- `Blob.rs`: `pub struct Inline` + `impl Inline` + `impl Default for
Inline` (48 lines). `rg 'InlineBlob|blob::Inline'` shows every reference
is inside a `//` comment; the `Any` enum at `Blob.rs:6353` has no
`Inline` variant.
- `Body.rs`, `server/RequestContext.rs`: removed the commented-out
`InlineBlob` match arms that referenced the deleted struct.
- `S3File.rs`: `has_instance` + `JSS3File__hasInstance` export (only
caller was the removed C++ static; see above).
- `streams.rs`: `BufferAction::get` (zero callers; all `.get()` calls on
`buffer_action` resolve to `JsCell::get`).
- `FileReader.rs`: `pub const TAG` (zero references).

## src/js (>6 months stale per `git blame`)

- `builtins/Ipc.ts`: 131-line commented-out `handleConversion` map
(net.Server/net.Socket/dgram.*). Blame 2025-05-06.
- `internal/fs/streams.ts`: commented-out `fastPath._getFd()` block.
Blame 2025-01-25.
- `node/worker_threads.ts`: two commented-out type imports. Blame
2023-08-07.

## Verification

- `bun bd`: builds clean
- `bun run rust:check-all`: 10 ok, 0 failed (all target triples)
- Smoke tests pass: `text-encoder.test.js` (42 pass),
`worker_threads.test.ts` (91 pass), `body.test.ts` (448 pass),
`url.test.ts` (18 pass), `performance.test.js` (7 pass)
-
`test/internal/source-lints/dead-symbols-webcore-inline-extern.test.ts`
added: fails on `main` (every check matches), passes after this diff

## Followups (not in this diff)

-
`BunFrontendDevServerFrontendDispatcher::clientErrorReported`/`graphUpdate`
and `LifecycleReporterFrontendDispatcher::reload` in the generated
inspector protocol are now unreferenced from C++; trimming those lives
in the protocol JSON, out of scope here. Downstream consumers that were
already inert before this PR (no Rust producer ever existed): the
`LifecycleReporter.reload` listener in
`packages/bun-vscode/src/features/diagnostics/diagnostics.ts`, the
`ReloadEvent` type in
`packages/bun-inspector-protocol/src/protocol/jsc/index.d.ts`, and the
two `test.todo` blocks waiting on `clientErrorReported`/`graphUpdate` in
`test/cli/inspect/BunFrontendDevServer.test.ts`.

Scanned and found clean (no confident dead symbols): `src/install/`
(lockfile/npm/migration/yarn/bin/isolated_install), `src/sql/postgres/`,
`src/http/` (websocket, h3_client, AsyncHTTP, HTTPThread, etc.),
`src/semver/`, `src/resolver/`, `src/ast/`, `src/collections/`,
`src/bun_core/`, `src/runtime/api/`, `src/runtime/node/`, `src/dotenv/`,
`src/patch/`, `src/glob/`, `src/event_loop/`, `src/threading/`,
`src/uws/`, `src/crash_handler/`, `src/valkey/`, `src/runtime/socket/`.

<!-- robobun:evidence:begin -->

---

**[review]** gate passed · iteration 2 · 46 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 5 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/dead-symbols-webcore-inline-extern.test.ts
bun test v1.4.0 (e434f18)

test/internal/source-lints/dead-symbols-webcore-inline-extern.test.ts:
23 |     ["src/jsc/bindings/webcore/JSMessageEventCustom.cpp", /#include "JSDOMWindow\.h"/],
24 |     ["src/jsc/bindings/webcore/JSMessageEvent.cpp", /#include "JSServiceWorker\.h"|#include "JSWindowProxy\.h"/],
25 |     ["src/jsc/bindings/webcore/JSEventTargetCustom.cpp", /"JSDOMWindow\.h"|"JSWindowProxy\.h"/],
26 |   ];
27 |   const found = checks.filter(([f, re]) => re.test(src(f))).map(([f, re]) => `${f}: ${re.source}`);
28 |   expect(found).toEqual([]);
                     ^
error: expect(received).toEqual(expected)

- []
+ [
+   "src/jsc/bindings/WebCoreOpaqueRoot.h: #include "Node\.h"",
+   "src/jsc/bindings/webcore/EventTargetHeaders.h: #include "Node\.h"",
+   "src/jsc/bindings/webcore/JSMessageEventCustom.cpp: #include "JSDOMWindow\.h"",
+   "src/jsc/bindings/webcore/JSMessageEvent.cpp: #include "JSServiceWorker\.h"|#include "JSWindowProxy\.h"",
+   "src/jsc
... (truncated)

release without fix: 5 FAILED
bun test v1.4.0-canary.1 (831f867)

test/internal/source-lints/dead-symbols-webcore-inline-extern.test.ts:
23 |     ["src/jsc/bindings/webcore/JSMessageEventCustom.cpp", /#include "JSDOMWindow\.h"/],
24 |     ["src/jsc/bindings/webcore/JSMessageEvent.cpp", /#include "JSServiceWorker\.h"|#include "JSWindowProxy\.h"/],
25 |     ["src/jsc/bindings/webcore/JSEventTargetCustom.cpp", /"JSDOMWindow\.h"|"JSWindowProxy\.h"/],
26 |   ];
27 |   const found = checks.filter(([f, re]) => re.test(src(f))).map(([f, re]) => `${f}: ${re.source}`);
28 |   expect(found).toEqual([]);
                     ^
error: expect(received).toEqual(expected)

- []
+ [
+   "src/jsc/bindings/WebCoreOpaqueRoot.h: #include "Node\.h"",
+   "src/jsc/bindings/webcore/EventTargetHeaders.h: #include "Node\.h"",
+   "src/jsc/bindings/webcore/JSMessageEventCustom.cpp: #include "JSDOMWindow\.h"",
+   "src/jsc/bindings/webcore/JSMessageEvent.cpp: #include "JSServiceWorker\.h"|#include "JSWindowProxy\.h"",
+   "src/jsc/bindings/webcore/JSEventTargetCustom.cpp: "JSDOMWindow\.h"|"JSWindowProxy\.h"",
+ ]

- Expected  - 1
+ Received  + 7

      at <anonymous> (/workspace/bun/test/internal/source-lints/dead-symbol
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/dead-symbols-webcore-inline-extern.test.ts
bun test v1.4.0 (e434f18)

test/internal/source-lints/dead-symbols-webcore-inline-extern.test.ts:
(pass) #includes of deleted webcore/bindings headers do not reappear [16.17ms]
(pass) dead extern C wrappers and cascaded methods do not reappear [17.51ms]
(pass) commented-out DOMJIT/BINDING_INTEGRITY blocks in webcore do not reappear [17.74ms]
(pass) dead InlineBlob struct and S3File hasInstance do not reappear [22.02ms]
(pass) commented-out handleConversion/fs-stream blocks in src/js do not reappear [7.89ms]

 5 pass
 0 fail
 5 expect() calls
Ran 5 tests across 1 file. [2.09s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 723ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/39] gen cpp.rs (cppbind)
[2/39] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 238 extern-C blocks audited
[3/39] gen JS modules (bundle-modules)
Preprocess modules (8651ms)
Bundle modules (39ms)
Postprocesss modules (34ms)
Bundle Functions (673ms)
Generate Code (18ms)

[9.43s] Bundled "src/js" for production
  2559 kb
  193 internal modules
  13 native modules
  90 internal functions across 19 files
[3/29] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
src/js/builtins/Ipc.ts                             | 131 ---------------------
 src/js/internal/fs/streams.ts                      |  13 --
 src/js/node/worker_threads.ts                      |   2 -
 src/jsc/bindings/DOMWrapperWorld.cpp               |   2 -
 .../InspectorBunFrontendDevServerAgent.cpp         |  28 -----
 .../bindings/InspectorBunFrontendDevServerAgent.h  |  16 ---
 src/jsc/bindings/InspectorLifecycleAgent.cpp       |  13 --
 src/jsc/bindings/InspectorLifecycleAgent.h         |   1 -
 src/jsc/bindings/JSDOMWrapper.cpp                  |   7 --
 src/jsc/bindings/JSS3File.cpp                      |  26 ----
 src/jsc/bindings/JSS3File.h                        |   2 -
 src/jsc/bindings/StrongRef.cpp                     |  10 --
 src/jsc/bindings/StrongRef.h                       |   2 -
 src/jsc/bindings/StrongRootBlock.h                 |  15 ---
 src/jsc/bindings/TextCodecASCIIFastPath.h          |  78 ------------
 src/jsc/bindings/TextCodecWrapper.cpp              |  26 ----
 src/jsc/bindings/WebCoreOpaqueRoot.h               |   2 -
 src/jsc/bindings/highway_strings.cpp               |  78 ------------
 src/jsc/bindings/webcore/EventTargetHeaders.h      |   1 -
 src/jsc/bindings/webcore/JSDOMException.cpp        |  30 -----
 src/jsc/bindings/webcore/JSDOMPromise.cpp          |   2 -
 src/jsc/bindings/webcore/JSDOMPromiseDeferred.cpp  |   4 -
 src/jsc/bindings/webcore/JSDOMURL.cpp              |  12 --
 src/jsc/bindings/webcore/JSDOMWindow.h             |   1 -
 src/jsc/bindings/webcore/JSErrorEvent.cpp          |  29 -----
 src/jsc/bindings/webcore/JSErrorHandler.cpp        |   3 -
 src/jsc/bindings/webcore/JSEventListener.cpp       |  27 -----
 src/jsc/bindings/webcore/JSEventTargetCustom.cpp   |   5 -
 src/jsc/bindings/webcore/JSMessageEvent.cpp        |   2 -
 src/jsc/bindings/webcore/JSMessageEventCustom.cpp  |   3 -
 src/jsc/bindings/webcore/JSPerformance.cpp         |  17 ---
 src/jsc/bindings/webcore/JSServiceWorker.h       
... (truncated)
```

</details>

**gate history** · 3 passed · 1 rejected · iteration 2

<details><summary>evidence per changed file</summary>

```
file                                                     reads  edits  tests
src/js/builtins/Ipc.ts                                       1      1      0
src/js/internal/fs/streams.ts                                1      1      0
src/js/node/worker_threads.ts                                1      1      0
src/jsc/bindings/DOMWrapperWorld.cpp                         1      1      0
src/jsc/bindings/InspectorBunFrontendDevServerAgent.cpp      2      2      0
src/jsc/bindings/InspectorBunFrontendDevServerAgent.h        1      1      0
src/jsc/bindings/InspectorLifecycleAgent.cpp                 2      2      0
src/jsc/bindings/InspectorLifecycleAgent.h                   1      1      0
src/jsc/bindings/JSDOMWrapper.cpp                            1      1      0
src/jsc/bindings/JSS3File.cpp                                3      3      0
src/jsc/bindings/JSS3File.h                                  1      1      0
src/jsc/bindings/StrongRef.cpp                               1      1      0
src/jsc/bindings/StrongRef.h                                 1      1      0
src/jsc/bindings/StrongRootBlock.h                           2      2      0
src/jsc/bindings/TextCodecASCIIFastPath.h                    1      0      0
src/jsc/bindings/TextCodecWrapper.cpp                        1      1      0
(+ 30 more files)
```

</details>

<!-- robobun:evidence:end -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Empirically re-derived which `test/expectations.txt` entries are still
needed by removing all 29 and running the full CI matrix ([build
87834](https://buildkite.com/bun/bun/builds/87834)).

## Result: 29 entries → 3

### Kept (3): still fail on the named lane

| entry | lane | observed failure (build 87834) |
|---|---|---|
| `test/bundler/native-plugin.test.ts` | WINDOWS | MSB8020: ClangCL
build tools not found (agent image gap) |
| `test/js/node/test/parallel/test-net-pingpong.js` | WINDOWS |
named-pipe half-close: count 1000 !== 1001 |
| `test/js/node/test/sequential/test-net-listen-shared-ports.js` | LINUX
| SO_REUSEPORT shared-listener semantics; passes on macOS/Windows |

### Deleted (6): vendored Node tests that fail deterministically on
every lane

These can never pass as vendored; removing the files instead of
re-quarantining.

| file | reason |
|---|---|
| `test-stream-wrap.js`, `test-stream-wrap-drain.js`,
`test-stream-wrap-encoding.js` | require `internal/js_stream_socket`
which Bun does not implement |
| `test-net-connect-keepalive.js`, `test-net-server-keepalive.js` |
assert `_handle.setKeepAlive` receives seconds (libuv convention); Bun's
`_handle` is Bun.Socket (ms). End-to-end TCP_KEEPIDLE coverage is in
`test/js/bun/net/socket.test.ts` |
| `test-set-http-max-http-headers.js` | spawns
`test-http-max-http-headers.js` which is not vendored |

### Moved to `no-validate-exceptions.txt` (7): fail only via
unchecked-exception assertions

These now **run** on ASAN with `validateExceptionChecks` off, instead of
being removed from the run entirely.

| file | unchecked exception scope |
|---|---|
| `test/integration/next-pages/test/dev-server-ssr-100.test.ts` |
`JSOrderedHashTable::getImpl` → `executeBoundCall` |
| `test/integration/next-pages/test/dev-server.test.ts` | same |
| `test/integration/next-pages/test/next-build.test.ts` | same |
| `test/js/third_party/next-auth/next-auth.test.ts` | same |
| `test/napi/napi.test.ts` | `Process_functionDlopen`
(BunProcess.cpp:397) |
| `test/cli/run/require-cache.test.ts` | `NapiClass::finishCreation`
(NapiClass.cpp:120) |
| `test/cli/inspect/inspect.test.ts` | `getOwnNonIndexPropertyNames` →
`JSObjectInlines::get` (inspector Runtime.evaluate) |

Also bumped `esm-fixture-leak-small.mjs` ASAN threshold 400→500 MB
(build 87834 measured 407 MB; ASAN quarantine overhead) so
`require-cache.test.ts` passes end to end on ASAN.

### Removed (13): now pass on their named lane

| entry | was scoped to | now passes on |
|---|---|---|
| `test/js/node/test/parallel/test-repl-close.js` | WINDOWS-AARCH64 |
windows 11 aarch64 |
| `test/js/node/test/parallel/test-tls-connect-memleak.js` |
LINUX-X64-MUSL | alpine 3.23 x64 + aarch64 |
| `test/js/bun/spawn/spawn-maxbuf.test.ts` | (all) | every lane (also
fixed for debug in #36782) |
| `test/js/bun/spawn/spawn.test.ts` | ASAN | every lane
(heap-use-after-free fixed in #36783) |
| `test/js/sql/tls-sql.test.ts` | ASAN | debian 13 x64-asan |
| `test/js/node/url/pathToFileURL.test.ts` | ASAN | debian 13 x64-asan |
| `test/js/node/fs/abort-signal-leak-read-write-file.test.ts` | ASAN |
debian 13 x64-asan |
| `test/js/web/streams/streams-leak.test.ts` | ASAN | debian 13 x64-asan
|
| `test/js/node/test/parallel/test-net-server-listen-path.js` | WINDOWS
| windows 2019 x64 + 11 aarch64 |
| `test/js/node/test/parallel/test-net-pipe-connect-errors.js` | WINDOWS
| fixed in #36786 |
| `test/js/node/test/parallel/test-net-client-bind-twice.js` | WINDOWS |
fixed in #36786 |
| `test/js/node/test/parallel/test-net-server-reset.js` | WINDOWS |
fixed in #36786 |
| `test/js/bun/io/fetch/fetch-abort-slow-connect.test.ts` | DARWIN |
darwin 26 aarch64 + 14 x64 |

### Caveats

- `test-tls-connect-memleak.js` and `fetch-abort-slow-connect.test.ts`
were `FLAKY` quarantines; both passed in probe build 87834 and
confirmation builds 87845 / 87860 / 87868.

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 4 · docs-only change; test-proof not
applicable

<!-- robobun:evidence:end -->
### What does this PR do?

Tightens the GitHub Actions workflows against the credential-theft
patterns behind this year's action supply-chain incidents (persisted
checkout tokens, floating tags, `${{ }}` in shell), and deletes
workflows that have been dead for a year or more — one of which
referenced the compromised `actions-cool/issues-helper@v3`.

| Change | Scope |
|---|---|
| `actions/checkout` → v7.0.1 SHA, `persist-credentials: false` | all 30
checkouts; homebrew tap push now passes its token explicitly |
| top-level `permissions: contents: read` | 27 workflows that had none
(job-level grants unchanged) |
| `${{ inputs/steps/env.* }}` moved from `run:` into `env:` | setup-bun
action, release, vscode-release, update-sqlite3, auto-assign-types,
format/clippy/miri |
| third-party actions dropped | `JS-DevTools/npm-publish` → `npm
publish`; `git-auto-commit-action` → plain git; `oven-sh/setup-bun`
v1/v2 → in-repo `setup-bun` |
| triage jobs | `anthropics/claude-code-action/base-action` v1.0.183
(old mirror stopped tagging), model `claude-opus-5` |
| Bun used by CI jobs | 1.3.14 |
| `release.yml` `is-latest` | read boolean `inputs.is-latest`;
`github.event.inputs.is-latest` is the string `"false"` (truthy), so
manual dispatches always published as latest |
| new `dependabot.yml` | github-actions, weekly, 7-day cooldown |

| Deleted | Why |
|---|---|
| `labeled.yml.disabled` | disabled Nov 2025; used
`actions-cool/issues-helper@v3` (compromised May 2026 — never ran here)
|
| `comment-lint.yml.disabled` | disabled Nov 2025; listened for a
workflow that no longer exists |
| `update-root-certs.yml` | disabled in the Actions UI since Aug 2025;
every run was `startup_failure` |
| `test-bump.yml`, `actions/bump`, release `bump` job |
`scripts/bump.ts` was removed in 2024 |

### How did you verify your code works?

`zizmor .github`: 54 → 5 findings; the remainder are three
`pull_request_target` workflows that never check out PR code and a
`GITHUB_PATH` append in setup-bun. All YAML parses. PR-triggered
workflows (lint/format/clippy/bun-types/packages-ci) exercise the
checkout/permissions/setup-bun changes on this PR.

Not exercisable until the next release: the `npm-types` publish step and
the homebrew tap push in `release.yml`.
Upgrades the WebKit fork to upstream WebKit/WebKit@3722912ff800
(2026-08-02) via oven-sh/WebKit#383.

oven-sh/WebKit#383 is merged; `WEBKIT_VERSION` points at the merge
commit `e6e37cda216c0292ae68c30c84a9dc8601d0fba5`.

## Bun-side changes

Upstream `90b2ecf79ae3` keys `m_loadedModules` on `(specifier,
ScriptFetchParameters::Type)` and threads the type through
`ImportEntry`, `ExportEntry`, `StarExportEntry`, and
`ModuleAnalyzer::appendRequestedModule`. Under `bun test --isolate` (the
`BunTranspiledModule` path), Bun'''s synthesized record must agree with
what JSC'''s own `ModuleAnalyzer` would produce from the printed source;
a mismatch fails the BUN_DEBUG record diff in debug and null-derefs
`hostResolveImportedModule` in release.

- `analyze_transpiled_module` / `js_printer`: every `RecordKind` now
carries one trailing `FetchParameters` slot. `add_import_info_*` and
`add_export_info_*` accept it; `finalize()` propagates the source
import'''s slot through the Local->Indirect conversion.
`RequestedModules` dedupes on `(specifier, Type, phase)`.
- `analyze_jsc.rs`: decodes the trailing slot to the JSC `Type` enum and
passes it to every `addImportEntry*` / `addIndirectExport` /
`addNamespaceExport` / `addStarExport`; buffer validation is per-slot so
only the trailing slot accepts the wider `FetchParameters` sentinel
range.
- `BunAnalyzeTranspiledModule.cpp`: all seven `addImportEntry*` /
`add*Export` functions take `uint8_t moduleRequestType` and set
`.moduleRequestType` explicitly; `dumpRecordInfo()` prints `type(N)` for
import/export/star entries; `static_assert` pins the ordinal values
`to_script_fetch_parameters_type()` hardcodes.
- `RuntimeTranspilerCache` `EXPECTED_VERSION` -> 25 (esm_record layout
change).

## WebKit-side changes (oven-sh/WebKit#383)

- 17 merge conflicts resolved in JSC/WTF; fork's
`USE(BUN_JSC_ADDITIONS)` hunks preserved.
- Fork `ffi/` code adapted to upstream's 32-bit removal (`USE_JSVALUE64`
macro deleted, `is64Bit()` removed, `payloadFor` -> `lowWordFor`).
- `InspectorDebuggerAgent.cpp`: handle `BunTranspiledModule`/`Synthetic`
in new `scriptTypeForScript` switch.
- Recorded `01aaa3e0be0c` as ancestor via `-s ours` (oven-sh/WebKit#352
was a squash merge).

## How did you verify your code works?

- `bun run jsc:build:debug` builds and the `jsc` shell runs (`-e
'print(42)'` -> `42`).
- `bun run build:local -p '42'` links and runs against the local merged
WebKit.
- oven-sh/WebKit#383 preview build green on all 38 platform variants.
- `bun bd -p 'process.versions.webkit'` -> `preview-pr-383-b9ea4dc5`.
- New test `test/js/bun/jsc/webkit-upgrade-3722912f.test.ts`: 4/4 pass
with `bun bd test`, 3/4 fail with `USE_SYSTEM_BUN=1 bun test` (old JSC
lacks `Iterator.prototype.includes`, returns `""` for cyclic join,
`WebAssembly.Exception.length === 1`).

## JavaScriptCore/WTF/bmalloc changes since 01aaa3e0be0c (Jul 25)

Upstream range: WebKit/WebKit@01aaa3e0be0c...3722912ff800 (474 commits
total, 110 touching JSC/WTF/bmalloc, Jul 25 → Aug 2 2026).

### Highlights

- `29ceb3c03de3` Remove 32-bit JSValues (JSVALUE32_64 and
`CPU(NEEDS_ALIGNED_ACCESS)` deleted).
- `857bd4334690` Remove ARMv7 JIT support (ARMv7 is CLoop-only now;
drops remaining 32-bit/x86 JIT refs).
- `232cebabc1f3` Remove big-endian support and platforms without
unaligned loads/stores (WTF + JSC).
- `6eaa5ac1f65d` Remove 32-bit libpas support.
- `bfb1b1183bc2` Remove the B3/Air graph-coloring register allocator
(greedy is the only allocator now).
- `0d0080ea539d` Enable WebAssembly Memory64 by default (+ Table64/SIMD
follow-ups).
- `f2f2c2ddf637` Remove `StringRecursionChecker`; cyclic
`toString`/`join` now throws RangeError via stack check (spec-correct).
- `319f94b3db4a` Enable `Iterator.prototype.includes()` by default.
- `90b2ecf79ae3` `hostResolveImportedModule` now honors import-attribute
`type` (module loader behavior change).
- `f5716f6401ed` Add `preserve_most` calling convention to fastMalloc
APIs on ARM64 (perf + ABI of WTF alloc entry points).

### JavaScriptCore

**Runtime / builtins**
- `f2f2c2ddf637` Remove StringRecursionChecker; rely on stack-overflow
checks.
- `cd91e7f128dd` Add fast flag for `ToPrimitive(Object)` calls in
runtime.
- `173a0bd6d937` Use `defaultToPrimitiveFastAndNonObservable` in
`JSObject::toString`.
- `960adeccefcd` Fast operation for `Array#shift`.
- `92c6650c7947` Add `JSArrayIterator::next` C++ helper.
- `cb1c48b3e95f` Extend `Array.from()` Set fast path to
`set.keys()/.values()`.
- `4a2e724d6789` Fix: `Array.from(set.keys()/.values())` fast path
ignored `Symbol.iterator` overrides.
- `73e4c589c1e6` Extend `StringSplitCache` to RegExp separators.
- `656d3c36830f` / `1d355c27ea88` 8-byte SWAR fast paths in
`JSON.stringify` string copy (same-type & upconvert).
- `9f9370cc729f` Fix JSON.stringify regression around `toJSON` check.
- `2eb77e9c9473` BigInt: implement Crandall reduction.
- `39b1bb9cfc85` BigInt: deploy Comba multiplication more broadly.
- `319f94b3db4a` Enable `Iterator.prototype.includes()`.
- `0731b27c1b60` `Iterator.zip`: use null-prototype objects for
options/underlying iterator.
- `90b2ecf79ae3` `hostResolveImportedModule` respects module request
import-attribute `type`.
- `4f54300b848a` Collect diagnostics when `getDirect` returns zero
JSValue in `llint_slow_path_get_by_id`.

**Parser / bytecompiler**
- `c2beca7a439f` Lexer: scan integer tokens in a single pass.
- `72ea806faa21` Use overflow-safe range when choosing a switch jump
table.

**LLInt / DFG / FTL / B3**
- `29ceb3c03de3` Remove 32-bit JSValues.
- `857bd4334690` Remove ARMv7 JIT support.
- `bfb1b1183bc2` Remove B3/Air graph-coloring register allocator.
- `68cde6ba2ad3` Make IRO (Air register allocation) faster.
- `83540481435f` DFG: allocate
`BasicBlock::intersectionOfPastValuesAtHead` only for OSR-entry targets.
- `1566615170ec` DFG fix: `EnumeratorNextUpdateIndexAndMode` must
require original array structure for `InBoundsSaneChain`.
- `e759fa9dd063` LLInt: inline hot path of `op_enter`.
- `9610c2113b45` offlineasm: emit ARM64 register-offset addressing for
BaseIndex operands.
- `4ebed2479144` Speed up `addSortedRange` via binary search.
- `1c006b0b0f62` Fix regex `setLastIndex` on 32-bit.

**WebAssembly**
- `0d0080ea539d` Enable Memory64 feature flag.
- `15aa6fad53e3` Memory64: SIMD support.
- `bf0425598904` Memory64: expand declared memory limits.
- `862994e2cc37` Memory64: validate table import address-type match.
- `184ee4c654bd` Memory64: Table64 in OMG tier.
- `d202bedc5ff6` Memory64: Table64 in BBQ tier.
- `bf6512f84f7d` Support `WebAssembly.Exception` `options.traceStack` (+
`stack` getter, ctor length = 2).
- `24527bbb9ac8` Optimize Wasm JITCallee publication (lock splitting,
icache barrier rework).
- `1803d6109d98` Speed up `WebAssembly.Table` construction.
- `d434a41411a3` BBQ: optimize `br_table` for consecutive same-target
runs.
- `51d3dbaa278e` IPInt: add `DEFINE_IPINT_THUNK_FOR_ENTRY`.
- `244cd98f7986` Fix `generateWasmOpsHeader.py` under non-UTF-8 locales.

**Yarr / RegExp**
- `581f1d958329` Start end-anchored fixed-size regexps at the only
possible position.
- `a458a6c1f0a7` v-mode class-set op loop: stop early when no more
output possible.
- `7c5dbbcba110` Extend `ParenthesesSubpatternTerminal`.
- `e1def8f4e5fd` Don't save sibling/ancestor-sibling frame slots for
`ParenContext`.
- `1e43057f135a` Extend first-character filter further.
- `54916608d7d6` Fix non-BMP advance latch; simplify
`tryReadUnicodeCharImpl`.
- `46a4b17efbe9` `optimizeBOL`: don't filter contents of negative
lookaheads.
- `b128ddd863ab` Fix dot-star-wrapped optimization for sticky patterns.
- `98d0367d2247` Fix: `^` inside a paren that can match empty does not
anchor the pattern.

**Intl / Temporal**
- `09917ef55b0f` Add missing `U_FAILURE(status)` check in
`actualLunisolarMonthLength`.

**Inspector**
- `3722912ff800` / `7be5445e4a22` / `e8c97076834e` / `f3e34dde7c9d`
Canvas: instrument & record WebGPU devices/pipelines.
- `301d6b2b21f7` Associate WebAssembly module scripts with the fetching
resource.
- `28b979b1659b` / `479edb2e4395` Site Isolation: implement
`Network.loadResource` / `Network.getSerializedCertificate`.

### WTF

- `232cebabc1f3` Remove big-endian support and
`CPU(NEEDS_ALIGNED_ACCESS)`.
- `e5fa5c604438` Upgrade fast_float to 8.2.10.
- `a288a8ec809b` Widen `find16`/`find32` SIMD threshold; faster ASCII
case-conversion prefix copy.
- `6cb1077d85c8` `makeStringByReplacingAll()` now uses SIMD-accelerated
`find()`.
- `5590f2e70615` Fix `AdaptiveStringSearcher` good-suffix shift table
off-by-one.
- `f5716f6401ed` Add `preserve_most` to most fastMalloc APIs on ARM64.
- `f7a9d16e1531` Add `removeIf()` to `WeakHashSet` / `WeakListHashSet`.
- `e1fc460b8f1d` Add `removeIf()` to `RobinHoodHashTable`.
- `ff1f31c83dc7` Treat creating/destroying a `CheckedPtr` as no-delete.
- `bf15f00ebe95` Remove 12 unused internal-linkage templates
(TypeTraits/HashTable/Vector/etc.).
- `5b84cf3719fa` Use `__builtin_trap` instead of inline asm under clang
static analyzer.
- `158f737725b7` Add helpers for Darwin temp/cache directories.
- `04e3d47960f3` Limit URL size at IPC boundary (Chrome/Blink parity).
- `5daad377031c` / `a7ea27dd3bb6` Enable `-Wthread-safety` on GTK/WPE
and fix findings.
- `7eb640d408f8` CMake: merge Mac and iOS ports into "Cocoa".
- `cfb222f3c4d1` CMake: run `cleandead` at end of configuration.

### bmalloc

- `6eaa5ac1f65d` Remove 32-bit libpas support.
- `f5716f6401ed` `preserve_most` on fastMalloc APIs (ARM64).
- `8b8b3e5ee16c` Fix inverted `MADV_ZERO` support latch in
`VMAllocate.cpp`.
- `ead6285911f6` libpas: `pas_thread_local_cache_for_all` clobbered its
should-go-again result.
- `90cbe5e85528` libpas: fix benign read from a deallocated TLC.
- `e388877954d1` PGM allocator: fix uninitialized `free_status`
misclassifying OOB as UAF.
- `05c83a6550b7` libpas: fix
`MTE_overrideEnablementForJavaScriptCore=true` incorrectly disabling
MTE.
- `f01297663d40` / `86fb5e3a4eef` / `d18773ec666e` libpas test coverage
(scavenging / zeroing / paged-out pages).

### Breaking/notable for Bun

- **32-bit purge**: `29ceb3c03de3` (JSVALUE32_64 removed),
`857bd4334690` (ARMv7 JIT removed), `6eaa5ac1f65d` (32-bit libpas
removed), `232cebabc1f3` (big-endian + `CPU(NEEDS_ALIGNED_ACCESS)`
removed). Any `#if USE(JSVALUE64)` / `CPU(ADDRESS32)` guards in Bun
patches are now dead.
- **`VM` layout**: `f2f2c2ddf637` deletes
`StringRecursionChecker.{h,cpp}` and the `stringRecursionCheck*` fields
from `VM.h`. Cyclic `Array.prototype.join`/`toString` now throws
`RangeError` instead of returning `""`.
- **Module loader**: `90b2ecf79ae3` changes `hostResolveImportedModule`
to propagate the import-attribute `type` — check Bun's module loader
hook signatures.
- **Register allocator**: `bfb1b1183bc2` removes the B3/Air
graph-coloring allocator and its `Options::` toggle.
- **fastMalloc ABI (ARM64)**: `f5716f6401ed` adds
`__attribute__((preserve_most))` to `fastMalloc`/`fastFree` etc. —
affects anything calling these across the WTF boundary on arm64.
- **BuiltinNames**: `bf6512f84f7d` registers `stackPrivateName` as
private-only; `WebAssembly.Exception` constructor `length` becomes 2 and
gains a `stack` prototype getter.
- **Feature defaults**: `0d0080ea539d` Wasm Memory64 on by default;
`319f94b3db4a` `Iterator.prototype.includes` on by default.
- **Wasm threading**: `24527bbb9ac8` reworks icache barrier / callee
publication and adds `Thread::barrierInstructionCache()` in WTF.

<!-- robobun:evidence:begin -->

---

**[decide:webkit]** gate passed · iteration 2 · 8 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: BUILD FAILED (no junit output)
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" "test/js/bun/jsc/webkit-upgrade-3722912f.test.ts"
ninja: Entering directory `/workspace/bun/build/debug'
[1/182] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 238 extern-C blocks audited
[2/182] gen cpp.rs (cppbind)
[3/182] gen JSSink.{cpp,h,lut.h,rs}
generated_jssink.rs: 7 sinks, 84 exported symbols
Generating /workspace/bun/build/debug/codegen/JSSink.lut.h from /workspace/bun/build/debug/codegen/JSSink.lut.txt
[4/182] gen JS modules (bundle-modules)
Preprocess modules (8715ms)
Bundle modules (63ms)
Postprocesss modules (24ms)
Bundle Functions (746ms)
Generate Code (12ms)

[9.57s] Bundled "src/js" for development
  2749 kb
  193 internal modules
  13 native modules
  90 internal functions across 19 files
[4/181] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

[177/181] cxx obj/unified/UnifiedSource-src_jsc_bindings-0.cpp.o
FAILED: obj/unified/UnifiedSou
... (truncated)

release without fix: all passed
bun test v1.4.0-canary.1 (385f528)

test/js/bun/jsc/webkit-upgrade-3722912f.test.ts:
(pass) WebKit 3722912ff800 upgrade > Iterator.prototype.includes is enabled by default (319f94b3db4a) [0.19ms]
(pass) WebKit 3722912ff800 upgrade > cyclic Array.prototype.join throws RangeError (f2f2c2ddf637) [2.51ms]
(pass) WebKit 3722912ff800 upgrade > WebAssembly.Exception gains options.traceStack and stack getter (bf6512f84f7d) [0.48ms]
(pass) WebKit 3722912ff800 upgrade > typed import attributes resolve through BunTranspiledModule (--isolate) (90b2ecf79ae3) [8.49ms]
(pass) WebKit 3722912ff800 upgrade > indirect, namespace and star re-exports link on the JSC ModuleAnalyzer path (90b2ecf79ae3) [22.09ms]

 5 pass
 0 fail
 12 expect() calls
Ran 5 tests across 1 file. [152.00ms]
__F:0:S:0
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" "test/js/bun/jsc/webkit-upgrade-3722912f.test.ts"
bun test v1.4.0 (59b0de0)

test/js/bun/jsc/webkit-upgrade-3722912f.test.ts:
(pass) WebKit 3722912ff800 upgrade > Iterator.prototype.includes is enabled by default (319f94b3db4a) [13.19ms]
(pass) WebKit 3722912ff800 upgrade > cyclic Array.prototype.join throws RangeError (f2f2c2ddf637) [10.75ms]
(pass) WebKit 3722912ff800 upgrade > WebAssembly.Exception gains options.traceStack and stack getter (bf6512f84f7d) [3.66ms]
(pass) WebKit 3722912ff800 upgrade > typed import attributes resolve through BunTranspiledModule (--isolate) (90b2ecf79ae3) [317.28ms]
(pass) WebKit 3722912ff800 upgrade > indirect, namespace and star re-exports link on the JSC ModuleAnalyzer path (90b2ecf79ae3) [1138.48ms]

 5 pass
 0 fail
 12 expect() calls
Ran 5 tests across 1 file. [3.17s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 676ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/140] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 238 extern-C blocks audited
[2/140] gen cpp.rs (cppbind)
[3/140] gen JSSink.{cpp,h,lut.h,rs}
generated_jssink.rs: 7 sinks, 84 exported symbols
Generating /workspace/bun/build/release/codegen/JSSink.lut.h from /workspace/bun/build/release/codegen/JSSink.lut.txt
[4/140] gen JS modules (bundle-modules)
Preprocess modules (8727ms)
Bundle modules (51ms)
Postprocesss modules (106ms)
Bundle Functions (687ms)
Generate Code (20ms)

[9.61s] Bundled "src/js" for production
  2559 kb
  193 internal modules
  13 native modules
  90 internal functions across 19 files
[4/139] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

^[[1m^[[92m   Compiling^[[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
^[[1m^[[92m   Compiling^[[0m bun_runtime v0.0.0 (/workspace/bun/src/runtime)
^[[1m^[[92m   Compilin
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
scripts/build/deps/webkit.ts                     |   2 +-
 src/bundler/analyze_transpiled_module.rs         |  38 +++--
 src/bundler/linker_context/postProcessJSChunk.rs |   8 +-
 src/bundler_jsc/analyze_jsc.rs                   | 135 ++++++++++++----
 src/js_printer/lib.rs                            | 191 +++++++++++++++++------
 src/jsc/RuntimeTranspilerCache.rs                |   5 +-
 src/jsc/bindings/BunAnalyzeTranspiledModule.cpp  |  39 +++--
 test/js/bun/jsc/webkit-upgrade-3722912f.test.ts  | 106 +++++++++++++
 8 files changed, 416 insertions(+), 108 deletions(-)
```

</details>

**gate history** · 5 passed · 1 rejected · iteration 2

<details><summary>evidence per changed file</summary>

```
file                                              reads  edits  tests
scripts/build/deps/webkit.ts                          1      1      0
src/bundler/analyze_transpiled_module.rs              3      3      0
src/bundler/linker_context/postProcessJSChunk.rs      1      1      0
src/bundler_jsc/analyze_jsc.rs                       13     15      0
src/js_printer/lib.rs                                 9     16      0
src/jsc/RuntimeTranspilerCache.rs                     2      3      0
src/jsc/bindings/BunAnalyzeTranspiledModule.cpp       8     12      0
test/js/bun/jsc/webkit-upgrade-3722912f.test.ts       2      5      0
```

</details>

<!-- robobun:evidence:end -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
### What does this PR do?

Removes every function-long `let x = unsafe { &mut *<callback param> };`
reborrow from the Rust codebase (~370 sites under any binding name,
census now 0) and adds a source lint
(`test/internal/source-lints/fn-long-mut-reborrow.test.ts`, runs on
every Rust PR) so the shape cannot come back. The line asserts exclusive
access for the whole function on objects whose callbacks re-enter them
through their own accessors — Stacked/Tree Borrows UB and an LLVM
`noalias` miscompile hazard. Where it was covering real re-entrancy, the
type is fixed instead of the call site:

| Fix shape | Applied to |
|---|---|
| `&self`-only API over `Cell`/`JsCell` state | `SendQueue`,
`RequestContext`, `FileResponseStream`, `MultiPartUpload`,
`UpgradedDuplex`, `SSLWrapper`, `WindowsNamedPipe`, `PathWatcher`,
`Channel`, GC controller |
| Raw `*mut Self` entries (callback may free the object; dispatch runs
with no receiver protector) | `PipeReader`
`read`/`on_poll`/`done`/`on_error`/`register_poll`/`stop_for_max_buffer`,
`DuplexUpgradeContext` `on_*`, `ProxyTunnel::shutdown` |
| Allocation-root pointer (`root: Cell<*mut Self>`) so pool/heap release
never deallocates through a `&self`-derived tag | `SendQueue`,
`Channel`, `WindowsNamedPipe`, `RequestContext` (request-pool `put`) |
| Move ownership out before dispatch instead of aliasing across
re-entrant JS | `MultiPartUpload` full-buffer part transfer |

Supporting changes: `BackRef`/`ParentRef` gain a `Shared`/`Mut`
write-provenance marker (writing through a `&T`-born backref is now a
type error); `bun_event_loop::AnyTask` (`*mut c_void` + fn pointer) is
deleted for typed `Taskable` tags; the `ErasedJsError` re-export alias
is deleted; the cron register/remove job scaffolding is deduplicated via
`CronJobBase` defaults.

Deliberate behavior changes: IPC decode loops no longer hold a buffer
borrow across user JS; subprocess buffered output is no longer
re-delivered after a streaming drain (fixes an output-doubling bug this
PR introduced mid-flight and CI caught); the test coordinator no longer
drops a `Channel`/`Process` from inside that object's own callback.

Out of scope, tracked follow-ups: `uv::StreamReader`'s `&mut Self`
methods, `PipeWriter`'s `LaunderedSelf` impls, the remaining `&mut self`
wrappers on `PosixBufferedReader` (`close`/`finish`/`start`/`watch`),
and the test coordinator's `ChannelOwner::on_channel_frame` chain
(`on_frame`/`assign_work` under a protected `&mut Worker`; `bail_out`'s
comment documents the reachability) — all pre-existing parent-chain
receivers, documented at the sites.

### How did you verify your code works?

`bun run rust:check-all` (10/10 targets incl. Windows), `bun run
rust:clippy` (clean), `bun run rust:miri -p bun_ptr` (18/18), the new
source lint at census 0. Debug build driven end to end: IPC round trips
(20k messages + disconnect), `Bun.serve`
bodies/`Bun.file`/streaming/abort/burst (104-test serve-file suite),
TLS-over-duplex (62 tests incl. close-UAF), S3 multipart, subprocess
`maxBuffer` overflow, cron register/validate, server
finalize-during-shutdown.

Benchmarked CI builds of this head vs its `main` merge-base (same
WebKit/mimalloc) on a 64-core Linux box: HTTP hello/echo, IPC, spawn, fs
streams, startup all within the measured ±1% noise floor;
anonymous/private-dirty RSS identical (3,444 kB both arms).

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Split from #36801 (1/5).

## What

When the threadsafe function was created with `func == NULL`, pass
`js_callback = NULL` to `call_js_cb` instead of an encoded `undefined`.

## Why (fixes #13771, closes #30543)

Node's `DispatchOne`
([src/node_api.cc#L452-L462](https://github.com/nodejs/node/blob/v26.x/src/node_api.cc#L452-L462)):

```cpp
napi_value js_callback = nullptr;
if (!ref.IsEmpty()) {
  v8::Local<v8::Function> js_cb = v8::Local<v8::Function>::New(env->isolate, ref);
  js_callback = v8impl::JsValueFromV8LocalValue(js_cb);
}
env->CallbackIntoModule<false>(
    [&](napi_env env) { call_js_cb(env, js_callback, context, data); });
```

When `ref` is empty (the caller passed `func = NULL` to
`napi_create_threadsafe_function`), `js_callback` stays `nullptr`. Bun
was passing `napi_value::create(env,
cb_js.get().unwrap_or(JSValue::UNDEFINED))`, which is the encoded
`undefined` value (a non-zero pointer). Addons such as napi-rs and
lightningcss test `if (js_callback != NULL)` and, seeing a non-null
value, try to `napi_call_function` it:

```
thread '<unnamed>' panicked at napi/src/threadsafe_function.rs:235:57:
called `Result::unwrap()` on an `Err` value: Error { status: InvalidArg, reason: "expect Function, got: Undefined" }
```

## Verification

New `checkSameOutput` test fails under the released Bun:

```
$ USE_SYSTEM_BUN=1 bun test test/napi/napi.test.ts -t "passes NULL js_callback"
- "js_callback == NULL: 1
+ "js_callback == NULL: 0
```

The `kMaxIterationCount` cap that was in the first revision of this PR
has been dropped: no issue has been filed about the unbounded dispatch
loop, and the cap as written did not yield to setImmediate/timers anyway
(`enqueue_task_concurrent` is drained inside the same `tick()`).

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 3 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/napi/napi.test.ts

<!-- robobun:evidence:end -->
### What does this PR do?

Fixes #36832.

`Bun.build({ splitting: true })` on a tree with chained `sideEffects:
false` barrel packages (e.g. `@sentry/node-core` re-exporting
`@sentry/core`) produced non-deterministic output, and in the unlucky
parse order silently dropped modules: the build reported `success: true`
while the emitted chunks referenced symbols no chunk declares, failing
at boot with

```
SyntaxError: Exported binding 'tK' needs to refer to a top-level declared variable.
```

### Cause

Barrel optimization defers a barrel's unused re-export records at parse
time and un-defers them later as requests for the names arrive
(`scheduleBarrelDeferredImports`). Two of those request paths depended
on parse-completion order:

1. A namespace request (`import * as ns from 'pkg-a'`) arriving after
the barrel was parsed only un-deferred the barrel's own records. The
names the barrel re-exports from an inner barrel (`export { beta } from
'pkg-b'`) were never requested from `pkg-b` when `pkg-b` had already
been parsed with a partial request set. `pkg-b`'s records stayed
deferred, the module bodies were dropped from the output, and the
namespace object still referenced their symbols. This is the
dropped-module case in the issue: the same 31 `@sentry/core` modules
missing, 18 orphaned minified bindings.
2. `export * from` targets are meant to be exempt from deferral (the
`IS_EXPORT_STAR_TARGET` check in `applyBarrelOptimization`), but the
flag only lands if the star exporter parses before the target. This is
what flapped the chunk graph (25 vs 32 chunks for identical input).

### Fix

In `src/bundler/barrel_imports.rs`:

- The BFS star branch now resolves un-deferred records inline and
propagates the request onward: each re-exported name is requested from
the module it comes from (by its original alias); namespace re-exports
and `export *` targets are requested as full-namespace.
- Star items mark a barrel as fully requested at most once, so the new
propagation terminates on `export *` cycles.
- `export * from` targets are recorded as fully requested when the star
exporter is processed, making the deferral decision independent of parse
order (same outcome the `IS_EXPORT_STAR_TARGET` flag produces when the
exporter happens to parse first).

### Verification

On the reporter's repro
(https://github.com/Karavil/bun-splitting-orphaned-exports), the unfixed
build produced three distinct outputs across runs (25-chunk good,
32-chunk variant, 25-chunk bad with 12 unlinkable chunks, ~2-4% of runs
on a 16-core box). With the fix, output is byte-identical across every
run, all chunks link when re-bundled individually, and the bundle boots.

New test `barrel/NamespaceImportUndefersChainedBarrels` reconstructs the
bad parse order deterministically: the file holding `import * as ns` is
large enough that the small barrel files always parse (and defer) first.
It fails on the unfixed build every time (module body missing from
output, boot fails) and passes with the fix. Existing barrel, splitting,
and edgecase bundler suites pass.

<!-- robobun:evidence:begin -->

---

**[review]** gate passed · iteration 1 · 2 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: BUILD FAILED (no junit output)
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/bundler/bundler_barrel.test.ts
ninja: Entering directory `/workspace/bun/build/debug'
[1/8] gen cpp.rs (cppbind)
[2/8] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 238 extern-C blocks audited
[2/8] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

[3/8] cxx obj/unified/UnifiedSource-src_jsc_bindings-0.cpp.o
FAILED: obj/unified/UnifiedSource-src_jsc_bindings-0.cpp.o 
/usr/bin/ccache /usr/lib/llvm-21/bin/clang++ -march=nehalem -O0 -g3 -gz=zstd -glldb -fsanitize=address -fno-exceptions -fno-c++-static-destructors -fno-rtti -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fvisibility=hidden -fvisibility-inlines-hidden -fno-unwind-tables -fno-asynchronous-unwind-tables -Wno-c23-extensions -ffunction-sections -fdata-sections -faddrsig -fno-semantic-interposition -fno-delete-null-pointer-checks -fdiagnostics-color=always -ferror-limit=100 -std=gnu++23 
... (truncated)

release without fix: all passed
bun test v1.4.0-canary.1 (5c9b728)

test/bundler/bundler_barrel.test.ts:
(pass) bundler > barrel/SkipUnusedWithOptimizeImports [13.95ms]
(pass) bundler > barrel/AllExportsNeeded [3.87ms]
(pass) bundler > barrel/SkipUnusedWithSideEffectsFalse [4.67ms]
(pass) bundler > barrel/NoOptimizationWithoutSideEffects [3.30ms]
(pass) bundler > barrel/ExportStarLoadsAll [2.62ms]
(pass) bundler > barrel/NonBarrelWithLocalExports [2.60ms]
(pass) bundler > barrel/NamespaceImportLoadsAll [2.71ms]
(pass) bundler > barrel/OutputEquivalence [3.66ms]
(pass) bundler > barrel/DefaultReExport [3.58ms]
(pass) bundler > barrel/ImportThenExport [3.44ms]
(pass) bundler > barrel/ReExportChain [3.52ms]
(pass) bundler > barrel/StarWithNamedFromSameSource [2.46ms]
(pass) bundler > barrel/SideEffectOnlyImport [3.32ms]
(pass) bundler > barrel/MultipleImporters [3.47ms]
(pass) bundler > barrel/CircularExports [11.45ms]
(pass) bundler > barrel/CircularStarExports [12.35ms]
(pass) bundler > barrel/NamespaceReExportCycleThroughStarTarget [13.21ms]
(pass) bundler > barrel/SelfReExport [6.58ms]
(pass) bundler > barrel/DynamicImportInSubmodule [4.67ms]
(pass) bundler > barrel/DynamicImportWithStaticImpor
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/bundler/bundler_barrel.test.ts
bun test v1.4.0 (2d34dde)

test/bundler/bundler_barrel.test.ts:
(pass) bundler > barrel/SkipUnusedWithOptimizeImports [602.47ms]
(pass) bundler > barrel/AllExportsNeeded [118.09ms]
(pass) bundler > barrel/SkipUnusedWithSideEffectsFalse [91.17ms]
(pass) bundler > barrel/NoOptimizationWithoutSideEffects [99.23ms]
(pass) bundler > barrel/ExportStarLoadsAll [84.00ms]
(pass) bundler > barrel/NonBarrelWithLocalExports [89.63ms]
(pass) bundler > barrel/NamespaceImportLoadsAll [87.82ms]
(pass) bundler > barrel/OutputEquivalence [109.01ms]
(pass) bundler > barrel/DefaultReExport [116.58ms]
(pass) bundler > barrel/ImportThenExport [111.44ms]
(pass) bundler > barrel/ReExportChain [94.02ms]
(pass) bundler > barrel/StarWithNamedFromSameSource [86.77ms]
(pass) bundler > barrel/SideEffectOnlyImport [111.60ms]
(pass) bundler > barrel/MultipleImporters [99.50ms]
(pass) bundler > barrel/CircularExports [372.92ms]
(pass) bundler > barrel/CircularStarExports [401.76ms]
(pass) bundler > barrel/NamespaceReExportCycleThr
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 681ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/9] gen cpp.rs (cppbind)
[2/9] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 238 extern-C blocks audited
[2/9] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m   Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
�[1m�[92m   Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m   Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd)
�[1m�[92m   Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp)
�[1m�[92m  
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
src/bundler/barrel_imports.rs       | 162 ++++++++++++++++++++++++++++++------
 test/bundler/bundler_barrel.test.ts | 112 +++++++++++++++++++++++++
 2 files changed, 250 insertions(+), 24 deletions(-)
```

</details>

**gate history** · 4 passed · 0 rejected · iteration 1

<details><summary>evidence per changed file</summary>

```
file                                 reads  edits  tests
src/bundler/barrel_imports.rs           10     34      0
test/bundler/bundler_barrel.test.ts      2      4      0
```

</details>

**root cause** · written by the author bot

The bundler's barrel import deferral failed to propagate namespace
requests through chained barrel re-exports, and `export * from` targets
were only marked fully requested when the exporter parsed before the
target, making module inclusion dependent on the non-deterministic
parallel parse order. When an unfavorable order occurred, deferred
modules were never scheduled and were silently dropped from the output
while other chunks still referenced their minified exports. The fix
propagates full-namespace and aliased requests through both named and
star re-exports during the BFS and seeds expor…

<!-- robobun:evidence:end -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…#36833)

Net -285 LOC (349 deletions, 64 insertions including the source-lint
test).

Each symbol was verified with `rg -w <symbol> src/ build/debug/codegen/`
to have zero references outside its own definition, then confirmed by a
full `bun bd` build and `bun run rust:check-all` across all 10 targets.

### `src/platform/darwin.rs` (whole file, 210 LOC)

The entire file duplicates `bun_sys::darwin`: the
`OSLog`/`Signpost`/`Interval` API and the `nocancel` extern block have
zero callers. `bun_perf` (the only signpost consumer) imports
`bun_sys::darwin::OSLog` and `bun_sys::darwin::os_log::signpost::*`.
`bun_platform` is force-linked only for `linux.rs`'s `#[no_mangle]`
export. Also drops the now-unused `bun_opaque`/`strum` deps from
`bun_platform/Cargo.toml` and the stale doc comment in `src/sys/lib.rs`
that pointed here.

### webcrypto C++

- `CryptoKeyHMAC::create` + const-ref ctor, `CryptoKeyAES::create` +
const-ref ctor: never called; `generate`/`importRaw`/`importJwk` all
construct via the rvalue ctor directly.
- `JSCryptoKey::fromJS`: declared, never defined, never called.
- `JSSubtleCrypto::toWrapped`: only reachable via
`convert<IDLInterface<SubtleCrypto>>`; no such call exists.
- `OpenSSLCryptoUniquePtr.h`: `X509Ptr`/`BIOPtr` aliases (zero refs) and
the `OPENSSL_VERSION_NUMBER >= 0x30000000L` block (BoringSSL defines
`0x1010107f`, so it never compiles, and no code references
`OsslParamBldPtr`/`OsslParamPtr`/`EVPKDFCtxPtr`/`EVPKDFPtr`).
- `CommonCryptoDERUtilities.h`: `extraBytesNeededForEncodedLength` is
only called from the same TU; header decl removed, made `static` in the
.cpp.
- `ScriptExecutionContext.h`: `wrapCryptoKey`/`unwrapCryptoKey` stubs
plus the commented-out virtual decls; leftover from the earlier
`SerializedCryptoKeyWrap` removal.

### sqlite / NodeVM C++

- `lazy_sqlite3.h`: `sqlite3_column_int` / `sqlite3_memory_used` /
`sqlite3_prepare16_v3` typedef+var+define+dlsym lines (code uses
`sqlite3_column_int64`, tracks memory via `sqlite_malloc_amount`, uses
`sqlite3_prepare_v3`).
- `SQLiteSingleton::schema_versions`: never read, never appended to.
-
`JSStatementSync::allowBareNamedParams`/`allowUnknownNamedParams`/`rowStructure`
getters: members are accessed directly.
-
`DOMIsoSubspaces`/`DOMClientIsoSubspaces::m_*subspaceForJSSQLStatementConstructor`:
`JSSQLStatementConstructor` lives in `JSFunction`'s subspace per the
`static_assert` in `JSSQLStatement.h`.
- `NodeVMGlobalObject::sigintReceived`: not virtual, never called;
`SigintWatcher::signalAll` invokes `vm().notifyNeedTermination()`
directly.
- `NodeVMModuleRequest::specifier`/`importAttributes` getters: `toJS`
reads `m_specifier`/`m_importAttributes` directly.

### Rust

- `bun_ast::PartTag::{JsxImport, CjsImports, ReactFastRefresh}`: never
assigned or compared.
- `bun_ast::flags::JSXElement::HasAnyDynamic`: never inserted or tested.
- `bun_ast::import_record::Tag::Tailwind`: last variant, never
constructed or matched.
- `bun_ast::BindingNodeList` type alias + its unused re-export in
`bun_js_parser::parser`.
- `bun_ast::StoreAstAllocHeap::reset`: callers invoke the free fn
`store_ast_alloc_heap::reset()` directly.
- `bun_jsc::JSPromise::reject_task`: sibling `resolve_task` has 4
callers, `reject_task` has zero.
- `bun_jsc::JSRuntimeType::UNDEFINED`: only `NOTHING` is referenced.

### src/js

- `readline.js`: unused `ObjectSetPrototypeOf` primordial destructure.
- `repl.js`: unused `ArrayPrototypeSlice` primordial destructure.
- `zlib.ts`: collapse redundant `ArrayBufferIsView` intermediate alias.

### Verification

- `bun bd` builds clean
- `bun run rust:check-all` passes all 10 targets (incl.
`aarch64-apple-darwin` for the `bun_platform` change)
- Smoke tests pass: `web-crypto.test.ts`, `sqlite.test.js`,
`node-sqlite.test.ts`, `zlib.test.js`, `transpiler.test.js`
-
`test/internal/source-lints/dead-symbols-platform-webcrypto-sqlite-vm.test.ts`
fails on main, passes on this branch

### Followups (not deleted; left for review)

- `src/runtime/api/bun/h2/connection.rs:1731-1941` outbound-stream API
(`begin_header_block`/`encode_header`/`send_header_block`/`send_data`/`send_push_promise`
+ transitively `SendWindow::{available,consume}`,
`Coder::{take_pending_size_update,encode}`, `write_table_size_update`,
~215 LOC): only called from `#[cfg(test)]`. File carries
`#![allow(dead_code)]` and was authored in #31584; likely intentional
WIP scaffolding for migrating `h2_frame_parser`'s outbound path.
- `src/jsc/bindings/webcrypto/*.idl` (29 files, ~1055 LOC): not
processed by any build step (`scripts/glob-sources.ts` globs only
`*.cpp`), but #34838 edited them recently so they may be maintained as
documentation.
- `src/jsc/ErrorCode.rs`: `Zig_ErrorCodeJSErrorObject` `#[no_mangle]`
static with zero refs in any `.cpp`/`.h` (sibling
`Zig_ErrorCodeParserError` is declared in `headers-handwritten.h`; this
one is not).

<!-- robobun:evidence:begin -->

---

**[review]** gate passed · iteration 0 · 34 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 3 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/dead-symbols-platform-webcrypto-sqlite-vm.test.ts
bun test v1.4.0 (e2a9bd9)

test/internal/source-lints/dead-symbols-platform-webcrypto-sqlite-vm.test.ts:
19 | function src(p: string): string {
20 |   return readFileSync(path.join(repoRoot, p), "utf8");
21 | }
22 | 
23 | test("bun_platform no longer declares a darwin module (duplicated bun_sys::darwin)", () => {
24 |   expect(src("src/platform/lib.rs")).not.toMatch(/pub mod darwin;/);
                                              ^
error: expect(received).not.toMatch(expected)

Expected substring or pattern: not /pub mod darwin;/
Received: "#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]\n#![warn(unused_must_use)]\n//! Per-OS APIs that don't fit in `bun_sys` (signposts, the `sys_epoll_pwait2` export).\n\n// Android is listed alongside Linux so the `#[no_mangle]` C exports\n// (`sys_epoll_pwait2`, …) reach the linker on the `*-linux-android` targets.\n#[cfg(target_os = \"macos\")]\npub mod darwin;\n#[cfg(any(target_os = \"linux\", t
... (truncated)

release without fix: 3 FAILED
bun test v1.4.0-canary.1 (1498d7b)

test/internal/source-lints/dead-symbols-platform-webcrypto-sqlite-vm.test.ts:
19 | function src(p: string): string {
20 |   return readFileSync(path.join(repoRoot, p), "utf8");
21 | }
22 | 
23 | test("bun_platform no longer declares a darwin module (duplicated bun_sys::darwin)", () => {
24 |   expect(src("src/platform/lib.rs")).not.toMatch(/pub mod darwin;/);
                                              ^
error: expect(received).not.toMatch(expected)

Expected substring or pattern: not /pub mod darwin;/
Received: "#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]\n#![warn(unused_must_use)]\n//! Per-OS APIs that don't fit in `bun_sys` (signposts, the `sys_epoll_pwait2` export).\n\n// Android is listed alongside Linux so the `#[no_mangle]` C exports\n// (`sys_epoll_pwait2`, …) reach the linker on the `*-linux-android` targets.\n#[cfg(target_os = \"macos\")]\npub mod darwin;\n#[cfg(any(target_os = \"linux\", target_os = \"android\"))]\npub(crate) mod linux;\n"

      at <anonymous> (/workspace/bun/test/internal/source-lints/dead-symbols-platform-webcrypto-sqlite-vm.test.ts:24:42)
(fail) bun_platform no long
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/dead-symbols-platform-webcrypto-sqlite-vm.test.ts
bun test v1.4.0 (e2a9bd9)

test/internal/source-lints/dead-symbols-platform-webcrypto-sqlite-vm.test.ts:
(pass) bun_platform no longer declares a darwin module (duplicated bun_sys::darwin) [10.05ms]
(pass) dead C++ symbols in webcrypto/sqlite/NodeVM do not reappear [23.34ms]
(pass) dead Rust symbols in ast/jsc do not reappear [14.33ms]

 3 pass
 0 fail
 3 expect() calls
Ran 3 tests across 1 file. [2.09s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 733ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/139] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 238 extern-C blocks audited
[2/139] gen cpp.rs (cppbind)
[3/139] gen JS modules (bundle-modules)
Preprocess modules (9279ms)
Bundle modules (59ms)
Postprocesss modules (259ms)
Bundle Functions (943ms)
Generate Code (32ms)

[10.59s] Bundled "src/js" for production
  2558 kb
  193 internal modules
  13 native modules
  90 internal functions across 19 files
[3/138] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
Cargo.lock                                         |   2 -
 src/ast/import_record.rs                           |   2 -
 src/ast/lib.rs                                     |   7 +-
 src/ast/nodes.rs                                   |   6 +-
 src/js/node/readline.js                            |   1 -
 src/js/node/repl.js                                |   1 -
 src/js/node/zlib.ts                                |   3 +-
 src/js_parser/parser.rs                            |   4 +-
 src/jsc/JSPromise.rs                               |  12 --
 src/jsc/JSRuntimeType.rs                           |   1 -
 src/jsc/bindings/NodeVM.cpp                        |   5 -
 src/jsc/bindings/NodeVM.h                          |   1 -
 src/jsc/bindings/NodeVMModule.h                    |   3 -
 src/jsc/bindings/ScriptExecutionContext.h          |  16 --
 src/jsc/bindings/sqlite/JSSQLStatement.cpp         |   2 -
 src/jsc/bindings/sqlite/NodeSqlite.h               |   3 -
 src/jsc/bindings/sqlite/lazy_sqlite3.h             |  18 --
 src/jsc/bindings/webcore/DOMClientIsoSubspaces.h   |   1 -
 src/jsc/bindings/webcore/DOMIsoSubspaces.h         |   1 -
 .../webcrypto/CommonCryptoDERUtilities.cpp         |   2 +-
 .../bindings/webcrypto/CommonCryptoDERUtilities.h  |   1 -
 src/jsc/bindings/webcrypto/CryptoKeyAES.cpp        |   7 -
 src/jsc/bindings/webcrypto/CryptoKeyAES.h          |   5 -
 src/jsc/bindings/webcrypto/CryptoKeyHMAC.cpp       |   7 -
 src/jsc/bindings/webcrypto/CryptoKeyHMAC.h         |   6 -
 src/jsc/bindings/webcrypto/JSCryptoKey.h           |   2 -
 src/jsc/bindings/webcrypto/JSSubtleCrypto.cpp      |   7 -
 src/jsc/bindings/webcrypto/JSSubtleCrypto.h        |   1 -
 .../bindings/webcrypto/OpenSSLCryptoUniquePtr.h    |  13 --
 src/platform/Cargo.toml                            |   2 -
 src/platform/darwin.rs                             | 210 ---------------------
 src/platform/lib.rs                                |   4 +-
 src/sys/lib.rs                    
... (truncated)
```

</details>

**gate history** · 1 passed · 0 rejected · iteration 0

<details><summary>evidence per changed file</summary>

```
file                                        reads  edits  tests
Cargo.lock                                      0      0      0
src/ast/import_record.rs                        1      1      0
src/ast/lib.rs                                  3      3      0
src/ast/nodes.rs                                2      2      0
src/js/node/readline.js                         1      1      0
src/js/node/repl.js                             1      1      0
src/js/node/zlib.ts                             1      1      0
src/js_parser/parser.rs                         1      1      0
src/jsc/JSPromise.rs                            1      1      0
src/jsc/JSRuntimeType.rs                        1      1      0
src/jsc/bindings/NodeVM.cpp                     1      1      0
src/jsc/bindings/NodeVM.h                       1      1      0
src/jsc/bindings/NodeVMModule.h                 1      1      0
src/jsc/bindings/ScriptExecutionContext.h       2      1      0
src/jsc/bindings/sqlite/JSSQLStatement.cpp      1      1      0
src/jsc/bindings/sqlite/NodeSqlite.h            2      2      0
(+ 18 more files)
```

</details>

<!-- robobun:evidence:end -->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…ntly (#36821)

## What

`test/cli/run/workspaces.test.ts` has three tests that each spawn an
independent `bun run --workspaces` subprocess in its own temp dir. This
tightens what each test asserts and wraps them in `describe.concurrent`
since they share no state.

## Assertion changes

10 `expect()` calls, up from 8:

- Check `stderr`/`stdout` before `exitCode` so failures print useful
output.
- Assert `stderr` is empty on both success paths (previously captured
but never checked).
- Happy path: compare the full stdout as a sorted line set instead of
two substring checks, so an unexpected extra line or format change
fails. This also subsumes the old `not.toContain("root test")` check.
- `--if-present`: also assert nothing is emitted for the package that
lacks the script.
- Error path: also assert `stdout` is empty and include the script name
in the expected error text.
- `await using proc = Bun.spawn(...)` for cleanup.

No tests removed or skipped.

## Timing

Local `bun bd test` (debug+ASAN), 5 runs each: median 2.74s before,
2.22s after.

debian-13 x64-asan CI lane: **11.99s** (build 88024) vs **11.06s**
(build 88091, this PR). The three subprocesses are CPU-bound under ASAN
so running them concurrently on the CI runner wins less than I initially
expected; the assertion tightening is the main value here.

<!-- robobun:evidence:begin -->

---

**[stamp-90s]** gate passed · iteration 0 · 1 files touched

<details><summary>passes on PR (with fix)</summary>

```console
Test-only change.

Debug/ASAN (expected pass):
$ bun bd test 'test/cli/run/workspaces.test.ts'
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/cli/run/workspaces.test.ts
bun test v1.4.0 (c8d83dd)

test/cli/run/workspaces.test.ts:
(pass) bun run --workspaces > runs script in all workspace packages [184.40ms]
(pass) bun run --workspaces > fails when no packages have the script [126.21ms]
(pass) bun run --workspaces > --if-present succeeds when script is missing [154.23ms]

 3 pass
 0 fail
 10 expect() calls
Ran 3 tests across 1 file. [2.22s]
Exit: 0
```

</details>

<details><summary>diff hotspot</summary>

```
test/cli/run/workspaces.test.ts | 186 +++++++++++++++++++++-------------------
 1 file changed, 98 insertions(+), 88 deletions(-)
```

</details>

**gate history** · 1 passed · 0 rejected · iteration 0

<details><summary>evidence per changed file</summary>

```
file                             reads  edits  tests
test/cli/run/workspaces.test.ts      4      4      0
```

</details>

<!-- robobun:evidence:end -->
Comment thread src/jsc/ErrorCode.rs
Comment thread src/jsc/HTTPServerAgent.rs
Comment thread src/jsc/JSArrayIterator.rs
Comment thread src/jsc/JSCScheduler.rs
Comment thread src/jsc/JSCScheduler.rs
Comment thread src/jsc/JSCScheduler.rs
Comment thread src/jsc/JSGlobalObject.rs
Comment thread src/jsc/JSPromise.rs
Comment thread src/jsc/JSPromise.rs
Comment thread src/jsc/JSPromise.rs
Comment thread src/jsc/JSPromise.rs
Comment thread src/jsc/JSRef.rs
Comment thread src/jsc/JSValue.rs
Comment thread src/jsc/JSValue.rs
Comment thread src/jsc/ModuleLoader.rs
Comment thread src/jsc/NodeCompileCache.rs
Comment thread src/jsc/NodeCompileCache.rs
Comment thread src/jsc/NodeCompileCache.rs
Comment thread src/jsc/NodeCompileCache.rs
Comment thread src/jsc/NodeCompileCache.rs
Comment thread src/jsc/NodeCompileCache.rs
Comment thread src/jsc/NodeCompileCache.rs
Comment thread src/jsc/NodeCompileCache.rs
Comment thread src/jsc/NodeCompileCache.rs
Comment thread src/jsc/NodeCompileCache.rs
Comment thread src/jsc/NodeCompileCache.rs
@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Note on the review threads and the Files tab: after main was merged into this branch (4b15a09), GitHub is still listing the whole merge as the PR's changes (1946 files). The diff against the actual merge base is 11 files:

git diff --stat origin/main...5fee0de865
 docs/bundler/executables.mdx        |   2 +
 src/ast/import_record.rs            |   4 +
 src/bundler/ParseTask.rs            |   8 +
 src/bundler/bundle_v2.rs            |  42 +
 src/js_parser/p.rs                  |  74 +
 src/js_parser/parse/parse_entry.rs  |   1 +
 src/js_parser/parser.rs             |   7 +
 src/js_parser/visit/visit_expr.rs   |  10 +
 src/resolver/resolver.rs            |  55 +
 test/bundler/bundler_loader.test.ts | 172 +-
 test/napi/napi.test.ts              |  59 +

The 120 comment-cop threads from the latest run were generated from that stale file list; all but one are on lines that belong to main (PipeReader, NodeCompileCache, bun_core strings, and so on), so I have resolved them without changes. The one on code this PR adds (import_record.rs, the NativeBindings doc comment) is answered inline.

Jarred-Sumner pushed a commit that referenced this pull request Aug 28, 2026
…austed (#39183)

### Problem

- The `Comment Cop` check is red on every claude-labeled PR whenever the
repo's GraphQL quota is used up. The step dies before it scans anything:
  ```
  GraphqlResponseError: Request failed due to following response errors:
   - API rate limit already exceeded for site ID installation.
  ##[error]Unhandled error: GraphqlResponseError: ...
  ```
with `x-ratelimit-resource: graphql`, `x-ratelimit-limit: 10000`,
`x-ratelimit-remaining: 0` (run
[31898901053](https://github.com/oven-sh/bun/actions/runs/31898901053)
on #39139). All 33 comment-cop failures on Aug 15 are this error (197
runs succeeded, 171 were skipped); they hit unrelated PRs at the same
time because the quota is shared by every workflow run in the repo, and
they come back whenever PR volume is high.
- Cause, `.github/workflows/comment-cop.yml:124` on main: the step reads
the PR's existing review threads with `github.graphql()`, outside any
`try`, and dedup and posting both sit behind that call. The REST call
just before it (`pulls.listFiles`) had succeeded in each failing run, so
the step had the diff; it only lacked the dedup data, which it was
reading from the exhausted quota.
- The same thread data also fed an auto-resolve of stale threads. That
part does not work: under the Actions `GITHUB_TOKEN` every
`resolveReviewThread` mutation fails with `Resource not accessible by
integration`, after which the step still logs `Resolved N stale
comment-cop thread(s).` Two successful runs from today:
[31900717034](https://github.com/oven-sh/bun/actions/runs/31900717034)
(20 attempted, 20 failed, "Resolved 20") and
[31900354924](https://github.com/oven-sh/bun/actions/runs/31900354924)
(4 attempted, 4 failed, "Resolved 4"). #36959 documents the same thing
and is the PR that moves resolution to a token that can do it. So the
GraphQL query was paying for one thing REST can provide and one thing
that does not happen.

### Fix

- Dedup reads the PR's review comments with `pulls.listReviewComments`
(REST) and collects the `<!-- comment-cop:KEY -->` markers from them.
Every comment the step posts starts with that marker, so the review
comments carry the same keys the thread roots did; REST is the quota the
step already needs for `listFiles` and `createReviewComment`, and it was
available in every failing run.
- The GraphQL query and the resolve loop are removed, so the step makes
no GraphQL request at all; the failure in the Problem section cannot
happen, rather than being caught. Nothing observable is lost: the
mutations the loop issued all fail today, and the only other thing it
did was log `Resolved N stale comment-cop thread(s).` after they had
failed. Resolving stale threads stays with #36959, which will also need
to move its thread lookup into its token step, since this PR removes the
`GITHUB_TOKEN` lookup it currently reuses (noted there).
- What the step posts is unchanged: the script on main and the REST
dedup were dry-run (real reads, writes recorded) against 9 PRs carrying
existing comment-cop threads (#35988, #36956, #36713, #35635, #33632,
#35596, #39139, #30609, #36959) and chose the same comments to post on
every one of them; the canned scenarios below show the same thing with
GraphQL working and with it exhausted.
- Test: `test/internal/source-lints/comment-cop.test.ts` extracts the
script from the workflow and runs it the way `actions/github-script`
does, against a fake `github` whose GraphQL requests all fail with the
rate limit error above. It checks that groups not yet flagged are posted
with the right line ranges, that a group already flagged (and a stale
marker) are left alone, that a second run recognizes the comments the
first run posted and posts nothing, and that no GraphQL request is made.
Both tests fail against the workflow on main (the script throws the
error above) and pass with this change. `source-lints.yml` now also
triggers on changes to `comment-cop.yml`, so the test runs whenever the
script is edited; it is excluded from the Buildkite shards like the rest
of that directory.
- Also ran: `bun bd test
test/internal/source-lints/comment-cop.test.ts`, `bun test
test/internal/source-lints/` (whole directory green), prettier on the
three files. The comment-cop run on this PR itself still executes the
script from main (`pull_request_target`), so the `Source lints` job is
the one that exercises the change here.
- Does not overlap with #37948 (which groups are flagged) or #38127
(where the file list comes from); both touch other parts of the script.
#36959 rewrites the block this PR deletes and will need a rebase either
way.

### Background

- Comment Cop (`.github/workflows/comment-cop.yml`): on each push to a
claude-labeled PR it reads the PR diff, finds multi-line comments added
under `src/`, and posts one review comment per comment block. Each bot
comment starts with `<!-- comment-cop:KEY -->`, KEY being the file path
plus a hash of the block's text; a block whose KEY is already on the PR
is not posted again. The check is advisory (not required for merge).
- GitHub API quotas: REST and GraphQL requests count against separate
hourly quotas (`x-ratelimit-resource` is `core` for REST and `graphql`
for GraphQL). For the `GITHUB_TOKEN` Actions hands out, each quota is
per repository, so every workflow run in oven-sh/bun draws on the same
two pools, and GraphQL-heavy automation (the `gh pr` / `gh issue` / `gh
search` commands used by other workflows go through GraphQL) empties the
GraphQL pool for everything else when PR volume is high.
- Review threads vs review comments: a review thread is GraphQL's
grouping of a line comment with its replies, and is the only place a
thread's id (what `resolveReviewThread` takes) and its resolved flag
exist. `GET /repos/{owner}/{repo}/pulls/{n}/comments` returns every
review comment on the PR, including each thread's root comment, so the
markers are reachable from REST; only resolving needs GraphQL, and under
`GITHUB_TOKEN` GitHub refuses that mutation regardless of the
`pull-requests: write` permission.

<details>
<summary>Script on main vs this branch against a fake github (the fake's
resolve mutation fails the way GITHUB_TOKEN's does)</summary>

```
main   | quota ok, stale threads present                        | exit 0                                   | posts ["src/foo.ts:5-6"] | resolve attempts ["T_STALE_OPEN"] | graphql ["query","query","mutation"] | warnings 1
main   | graphql quota exhausted, stale threads present         | step fails (unhandled rate limit error)  | posts [] | resolve attempts [] | graphql ["query"] | warnings 0
main   | graphql quota exhausted, nothing stale                 | step fails (unhandled rate limit error)  | posts [] | resolve attempts [] | graphql ["query"] | warnings 0
main   | graphql quota exhausted, PR has no review comments yet | step fails (unhandled rate limit error)  | posts [] | resolve attempts [] | graphql ["query"] | warnings 0
fixed  | quota ok, stale threads present                        | exit 0                                   | posts ["src/foo.ts:5-6"] | resolve attempts [] | graphql [] | warnings 0
fixed  | graphql quota exhausted, stale threads present         | exit 0                                   | posts ["src/foo.ts:5-6"] | resolve attempts [] | graphql [] | warnings 0
fixed  | graphql quota exhausted, nothing stale                 | exit 0                                   | posts ["src/foo.ts:5-6"] | resolve attempts [] | graphql [] | warnings 0
fixed  | graphql quota exhausted, PR has no review comments yet | exit 0                                   | posts ["src/foo.ts:2-3","src/foo.ts:5-6"] | resolve attempts [] | graphql [] | warnings 0
```

</details>

<details>
<summary>Dry-run against live PRs: comments the script on main would
post vs the REST dedup (real reads through a user token, writes
recorded)</summary>

```
PR #35988: same posts (0 vs 0)        245 threads on the PR are stale; main attempts to resolve them, this branch does not
PR #36956: same posts (0 vs 0)
PR #36713: same posts (1832 vs 1832)  the stale cached file list that #38127 fixes; identical on both
PR #35635: same posts (0 vs 0)
PR #33632: same posts (0 vs 0)
PR #35596: same posts (0 vs 0)
PR #39139: same posts (1 vs 1)        the comment the failing run above did not get to post
PR #30609: same posts (0 vs 0)
PR #36959: same posts (0 vs 0)
```

</details>

<details>
<summary>Headers from the failing run</summary>

```
errors: [ { type: 'RATE_LIMIT', code: 'graphql_rate_limit',
            message: 'API rate limit already exceeded for site ID installation.' } ]
variables: { owner: 'oven-sh', repo: 'bun', pr: 39139, after: null }
'x-ratelimit-limit': '10000'
'x-ratelimit-remaining': '0'
'x-ratelimit-resource': 'graphql'
'x-ratelimit-used': '10000'
```

</details>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

5 participants