Skip to content

install: fold the open bun install / pm / pack+publish PRs into one branch (171 PRs) - #39403

Closed
Jarred-Sumner wants to merge 259 commits into
mainfrom
claude/install-mega
Closed

Jarred-Sumner wants to merge 259 commits into
mainfrom
claude/install-mega

Conversation

@Jarred-Sumner

@Jarred-Sumner Jarred-Sumner commented Aug 17, 2026 •

Copy link
Copy Markdown
Collaborator

Folds the open bun install / bun pm / pack+publish PRs from Aug 12–17 into one branch so they can be reviewed and landed together. Each original PR is one squashed commit here (<title> (#N)), in dependency order, so git log/git blame attribute every hunk to its PR; conflict resolutions between overlapping PRs live in the later PR's commit. Cross-PR API reconciliation is in the mega-merge fixup: commits at the end, so the per-PR commits after #38804 (e2cf8c4305) do not build on their own until 0adcafaf8b — bisect across the fold with --first-parent on the fixups, not inside it.

171 PRs folded, 3 excluded (see below); #38557 was re-ported by hand onto the newer audit_command.rs. A simplification pass then roughly halved the net src/ addition (see below). cargo check --workspace and cargo clippy --workspace are clean, the debug build links, and all 94 test/cli/install/** files pass locally on Linux (debug+ASAN); see Verification.

Not folded

PR Why
#37925 semantic conflict with #39002 (which needs optional peers bound at load); #39002 is newer and explicitly calls this out
#38777 security screen: make_executable() follows an absolute bin-target symlink out of a file:/link: package and chmods the real file (CVE-2019-16775 class) when the folder dep resolves to an ancestor dir; needs rework before merging
#38810 dropped on request: installing a git dependency's devDependencies before prepare is rarely needed and runs a nested install (fetching and, via prepare, executing third-party devDependencies chosen by the git repo) outside the project's bunfig protections — too much attack surface for the benefit

Security screen

Every folded diff was reviewed for regressions (path traversal, credential leakage, TLS/integrity weakening, lifecycle-script trust widening, terminal injection, new unsafe on untrusted input). One PR was dropped for it (#38777 above) and one more on review (#38810). Items a reviewer may still want to eyeball — none were judged exploitable regressions:

  • bunx: serialize concurrent installs into the shared cache dir with a file lock #37851 (concern, low): bunx serialises concurrent installs into the shared /bunx-- dir with an flock on /.bunx-lock; the lock file is opened by path with O_CREAT|O_RDWR and without O_NOFOLLOW, adding a small symlink-following primitive inside the pre-existing TOCTOU window of the cache-dir trust check.
  • install: stamp lockfileVersion 1 on new bun.lock files #38741 (concern, low): New bun.lock files are stamped lockfileVersion 1 instead of 2, so the two v2-only parse-time fail-closed checks (off-registry npm tarball URL without a supported integrity hash; unsafe git .bun-tag) no longer apply to any lockfile created after this lands. The reduction is defence-in-depth only: the checks are keyed on a digit the lockfile tamperer also controls, main already stamps v1 whenever bun's own output would trip them, a tamperer can supply a matching integrity for their own tarball anyway, and the git tag is re-validated at checkout/extract time (repository.rs:923, extract_tarball.rs:484; github tags stay checked at every version).
  • install: fix default-trusted lifecycle scripts being blocked after yarn.lock migration #38795 (concern, low): Default-trusted check now ignores the URL fragment; the claim that this is exactly as strict as before is slightly off because bun_url lets a ? after # leak the fragment into the request path, though no attacker-chosen tarball results.
  • install: resolve .npmrc //host/ credentials by request URL #38800 (concern, low): .npmrc //host/path/ credentials are now resolved by request URL (npm nerf-dart semantics): scopes without credentials pick them up by registry URL, and tarballs on a host other than the registry get that host's own .npmrc credentials. Scope credentials still never leave the scope origin, but the by-URL match is scheme-agnostic and path-unnormalized, which slightly widens where a registry-controlled dist.tarball can make bun present a token.
  • install: migrate a package's own file: directories from package-lock.json relative to the package #38990 (concern, low): package-lock file: targets inside a registry/tarball/git package are now migrated as package-relative folder rows without normalising the sub-path, so a hand-edited key with .. is no longer skipped at migration time and containment relies on the installer (hoisted linker checks it, with an overrides-name exemption; isolated linker does not).
  • install: don't issue openat2/fchmodat2 on Android (seccomp SIGSYS) #39084 (concern, low): Skips openat2/fchmodat2 on Android (kernel release contains 'android' OR both ANDROID_ROOT and ANDROID_DATA env vars set); symlink-safety semantics are preserved by the fallbacks, but the env-var trigger lets any environment downgrade RESOLVE_BENEATH/RESOLVE_IN_ROOT hardening on ordinary Linux.

Product-behaviour changes that need a maintainer decision before this lands: #38741 (new bun.lock files are stamped lockfileVersion: 1 again — its own PR defers this to a maintainer, and it obsoletes the open #36464; the sibling snapshots are already re-stamped inside the fold, so pulling it back out is one revert + fixlv), #38797 (libc filtering needs the full registry document for every optional dependency on every OS, since npm's abbreviated document omits libc; measured: @esbuild/linux-x64 43 KB → 54 KB gzipped, @rollup/rollup-linux-x64-gnu 40 KB → 58 KB, one-time per package per manifest cache, in exchange for not downloading the other libc's multi-MB tarballs on Linux and a host-independent lockfile), #38804 (a migrated lockfile and its package.json edits are only written when the command saves a lockfile), #38923 (bun pm ls lists what is installed and exits 1 without node_modules), #38813 (pack/publish resolve workspace:/catalog: from package.json files, not bun.lock).

Simplification pass (after folding)

The PRs were written independently, so the folded diff carried duplicate helpers and a lot of narrative comments. A follow-up pass (the simplify(...) commits) cut the src/ diff against main from +12,081 / −6,723 (net +5,358) to +10,284 / −7,559 (net +2,725) with no behaviour change intended and the full test/cli/install/** suite green before and after. Main items:

  • One Lockfile::packages_named / package_satisfying / patched_package_satisfying replaces nine "package_index → first candidate whose resolution satisfies a range and a predicate" helpers spread over lockfile.rs, bun.lock.rs, PackageManagerEnqueue.rs, update_transitive.rs and prune.rs.
  • update_transitive.rs (Hold a transitive update that would re-fork a deduped package #38919/install: keep one copy of a package when one version satisfies every range #38770) no longer re-implements the resolver's rules: it calls keep_locked_if_ahead, is_named_update_row (split out of should_update) and dedupe::applied_override directly, and its Plan/Planned/InstanceEdges/KeepLocked scaffolding is gone; reachability uses lockfile::reachable.
  • One checked NUL-terminated join (resolve_path::join_abs_string_buf_z_checked) and one spill helper replace bin.rs::join_z_checked, PackageManager::join_path_z, and pack's normalize_buf_spill; the bunx install lock (bunx: serialize concurrent installs into the shared cache dir with a file lock #37851) is a single InstallLock RAII type.
  • Isolated linker: one append_store_path_at(buf, entry, Which) family; PackageInstall::destination_dir_subpath_buf and the DestinationSubpath guard removed in favour of join_z_spill; staging installs re-target a PackageInstall instead of threading dest_subpath through every backend; remove_link shared.
  • yarn.rs: one dependency-section table instead of four loops, git URL parsing folded into Entry::set_git_source, root/workspace package.json parsing shared; pnpm.rs: one merge-based copy_into_root, one workspaces object path; finish_migration builds the one error shape.
  • Options/registry: RegistryPath, set_scope_registry and the second credential lookup removed in favour of scope_for_package_name + UrlAuth::find; fmt::for_terminal(x) = EscapeControlChars(Redacted(x)) at every call site; MinimumReleaseAgeExcludes is one list.
  • Multi-line justification comments added by the PRs trimmed to one line or removed throughout (SAFETY lines kept).

Two small behaviour fixes fell out of the review agents' cross-checks and are their own commits: bun audit fix now rewrites a matching rule in resolutions as well as overrides (the parser reads both since #38811), and a failed root package.json write says failed to write package.json: <errno> instead of for workspace ''.

Conflict resolutions that changed a PR's code (not just context)

Folded PRs

Peer binding, hoisting, bun.lock load/round-trip, resolver dedupe (21)

bun update / bun add package.json edits, overrides (7)

Lockfile migration (yarn / pnpm / npm) (17)

file: / link: / tarball / git local dependencies (16)

Path-buffer overflows: panic → error (15)

Terminal control-character escaping and credential redaction (9)

Registry auth, .npmrc, proxies, URL parsing (16)

bun pm subcommands, audit, help text (14)

bun pm pack / bun publish (13)

Linkers, bins, node_modules layout (9)

Lifecycle scripts, env, minimum-release-age, semver, auto-install (16)

Test-only changes (13)

Platform-specific (5)

Verification

  • cargo check --workspace
  • cargo clippy --workspace --no-deps
  • debug build (Linux x64, ASAN)
  • test/cli/install/**, test/cli/run/**, test/regression/issue/*install* locally with the debug build. Remaining local-only failures are environmental and reproduce on main in this sandbox: bun-pm.test.ts read-only package.json cases (run as root; nobody cannot exec a binary under /root), thread-spawn-failure (LD_PRELOAD shim vs ASAN).
  • CI (first run on the pre-simplification head: 3 failing files, all addressed above — thread-spawn shim under ASAN, pnpm read-only package.json wording, update-security-provider range)

robobun and others added 30 commits August 17, 2026 01:04
…nd keep namedRegistries credentials out of bun.lock (#38997)
Jarred-Sumner pushed a commit that referenced this pull request Aug 21, 2026
### Problem
- `bun pm ls` without `--all` prints a root dependency once per entry
the root has for it. A workspace `a` that is also in the root's
`devDependencies` prints twice under a `node_modules (1)` header. So
does a name declared in two groups (`typescript` in #8283's example).
- Cause: `src/runtime/cli/package_manager_command.rs:644` prints the
root's dependency list as is: one entry per declaration plus one per
workspace member. `node_modules` has one folder per name.

### Fix
- Stable sort the root's entries by name and drop the later entries of a
name (`dedup_by`). `--trusted` filters the result.
- Correct because one name is one `node_modules/<name>` folder, the same
collapse `Tree::hoist_dependency` applies. The list is in the tree
builder's order, so the entry kept is the one the tree kept. An alias
has its own name and keeps its row. The listing does not read the stored
tree, so an older `bun.lockb` prints as before (Notes).
- Verified: `test/cli/install/bun-pm.test.ts`. Three new tests
(bun.lock, bun.lockb, `--trusted`) fail on the released bun. One more
pins that a root optional peer bound to a dependency's copy stays
listed. On this repo's lockfile the only change is the removed
`@types/bun` row.

### Background
- Each package owns one range of `buffers.dependencies`. The root's
range holds its declared dependencies plus one `Behavior::WORKSPACE`
entry per workspace member, sorted by group and name. Entries of one
name share one folder.
- The hoisted tree (`buffers.trees`, `buffers.hoisted_dependencies`)
models `node_modules`: one tree per folder, one entry per name. `--all`
prints it and the header `(N)` counts its entries. The default listing
prints only the root's range.

<details><summary>Notes</summary>

Repro without a registry:

```sh
mkdir -p d/packages/a && cd d
echo '{"name":"root","workspaces":["packages/*"],"devDependencies":{"a":"workspace:*"}}' > package.json
echo '{"name":"a","version":"1.0.0"}' > packages/a/package.json
bun install && bun pm ls
```

Released bun prints `a@workspace:packages/a` on two lines, three when
`a` is also in `dependencies`. `bun pm ls --all` prints it once. `bun pm
ls --trusted` with `trustedDependencies: ["a"]` prints it twice. In this
repo `bun pm ls` prints `@types/bun@workspace:packages/@types/bun`
twice.

- The first version of this PR printed the tree's root folder filtered
to the root's id range. A self-review found a case it drops: a root
optional peer that a dependency provides. When the tree builder binds
that peer late, it stores the folder entry under the provider's
dependency id (`Tree.rs`, `ResolveReplace`). bun 1.4.0 re-hoists on load
and on a second pass stores the root's own id, but a `bun.lockb` written
by bun 1.3.x keeps the provider's id, and bun does not rewrite a
lockfile that needs no changes. Checked with a `bun.lockb` written by
bun 1.3.14 from the `optional-peer-hoist-provider` and `-target`
registry fixtures: the tree version listed only the provider, the
current version lists both, as 1.3.14 and the released 1.4.0 do. The new
optional peer test covers the fresh lockfile shape of this case.
- Dedupe by package id was rejected: `bar` and `bar-alias: npm:bar`
share one package id but are two folders. The test keeps both rows.
- The `--trusted` test uses bun.lock only. bun.lockb stores only hashes
of `trustedDependencies`, and `has_trusted_dependency` does not trust a
hash alone since #31339, so `--trusted` lists nothing from a bun.lockb.
Released bun does the same. Not related to this change.
- A real conflict (workspace `a` plus `"a": "file:..."` at the root)
does not reach `pm ls` today: `bun install` writes a duplicate package
path and the lockfile fails to load (the area of #33156).
- #8283 asks for grouped output and `--json`. This PR removes the
duplicate row in its example and nothing else, so it does not close that
issue.
- Open PRs that touch this branch and do not fix this: #38923 (filter by
what is on disk), #38952 (header wording). The fold branch of #39403 has
the same loop.
- Suites run with the debug build: `test/cli/install/bun-pm.test.ts` (22
pass), `bun-install-lifecycle-scripts.test.ts -t trusted` (55 pass),
`bun-install-registry.test.ts -t "migration is out of sync"`,
`test/regression/issue/24502`. `cargo fmt --check` is clean. `bun pm ls
--all` on this repo is unchanged byte for byte.
</details>

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

---

**[review]** gate passed · iteration 0 · 2 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/cli/install/bun-pm.test.ts
bun test v1.4.0 (6e906e4)

test/cli/install/bun-pm.test.ts:
(pass) should list top-level dependency [386.82ms]
(pass) should list all dependencies [333.90ms]
(pass) should list top-level aliased dependency [329.02ms]
(pass) should list aliased dependencies [346.24ms]
(pass) should list only trusted dependencies with --trusted [464.07ms]
(pass) should list only trusted dependencies with --all --trusted [339.68ms]
(pass) should list trusted transitive dependencies under untrusted parents with --all --trusted (isolated) [356.01ms]
(pass) should list nothing with --trusted when no dependencies are trusted [346.58ms]
597 |   expect(await exists(join(package_dir, lockfile))).toBeTrue();
598 |   urls.length = 0;
599 | 
600 |   const [stdout, stderr, exitCode] = await spawnAndCollect("pm", "ls");
601 |   expect(stderr).toBe("");
602 |   expect(normalizeBunSnapshot(stdout, package_dir)).toMatchInlineSnapshot(`
                                                          ^
error: expect(received).toMatchInlineSnaps
... (truncated)

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

test/cli/install/bun-pm.test.ts:
(pass) should list top-level dependency [17.59ms]
(pass) should list all dependencies [15.04ms]
(pass) should list top-level aliased dependency [18.13ms]
(pass) should list aliased dependencies [26.89ms]
(pass) should list only trusted dependencies with --trusted [29.53ms]
(pass) should list only trusted dependencies with --all --trusted [30.36ms]
(pass) should list trusted transitive dependencies under untrusted parents with --all --trusted (isolated) [19.13ms]
(pass) should list nothing with --trusted when no dependencies are trusted [20.57ms]
597 |   expect(await exists(join(package_dir, lockfile))).toBeTrue();
598 |   urls.length = 0;
599 | 
600 |   const [stdout, stderr, exitCode] = await spawnAndCollect("pm", "ls");
601 |   expect(stderr).toBe("");
602 |   expect(normalizeBunSnapshot(stdout, package_dir)).toMatchInlineSnapshot(`
                                                          ^
error: expect(received).toMatchInlineSnapshot(expected)

  
  "<dir> node_modules (5)
  ├── bar@0.0.2
+ ├── bar@0.0.2
  ├── bar-alias@0.0.2
  ├── ws-once@workspace:packages/ws-once
+
... (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/cli/install/bun-pm.test.ts
bun test v1.4.0 (6e906e4)

test/cli/install/bun-pm.test.ts:
(pass) should list top-level dependency [381.86ms]
(pass) should list all dependencies [333.18ms]
(pass) should list top-level aliased dependency [351.49ms]
(pass) should list aliased dependencies [347.67ms]
(pass) should list only trusted dependencies with --trusted [463.21ms]
(pass) should list only trusted dependencies with --all --trusted [348.99ms]
(pass) should list trusted transitive dependencies under untrusted parents with --all --trusted (isolated) [321.16ms]
(pass) should list nothing with --trusted when no dependencies are trusted [309.86ms]
(pass) should list a workspace the root also depends on once (bun.lock) [541.88ms]
(pass) should list a workspace the root also depends on once (bun.lockb) [314.14ms]
(pass) should list a trusted workspace the root also depends on once with --trusted [315.39ms]
(pass) should list a root optional peer that a dependency provides [328.74ms]
(pass) should remove all cache [465.82ms]
(pass) bun pm m
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 683ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/5] gen generated_host_exports.rs
generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 240 extern-C blocks audited
[1/5] 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_runtime v0.0.0 (/workspace/bun/src/runtime)
�[1m�[92m   Compiling�[0m bun_bin v0.0.0 (/workspace/bun/src/bun_bin)
�[1m�[92m    Finished�[0m `release` profile [optimized + debuginfo] target(s) in 4m 36s
[2/5] link bun-profile
[4/5] strip bun
[4/5] bun-profile --revision
1.4.0-canary.1+b263001b0
[build] done
bun test v1.4.0-canary.1 (b263001)

test/cli/install/bun-pm.test.ts:
(pass) should list top-level dependency [15.40ms]
(pass) should list all dependencies [11.19ms]
(pass) should list top-level aliased dependency [9.40ms]
(pass) should list aliased dependencies [9.71ms]
(pass) should list only trusted dependencies with --trusted [10.18ms]
(pass) should list on
... (truncated)
```

</details>

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

```
src/runtime/cli/package_manager_command.rs |   8 +-
 test/cli/install/bun-pm.test.ts            | 150 ++++++++++++++++++++++++++++-
 2 files changed, 152 insertions(+), 6 deletions(-)
```

</details>

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

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

```
file                                        reads  edits  tests
src/runtime/cli/package_manager_command.rs      5      4      0
test/cli/install/bun-pm.test.ts                10      9      0
```

</details>

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

The root package's dependency list receives one entry per workspace
member plus one per declared dependency, so a workspace that the root
also declares as a dependency has two entries resolving to the same
package, and the default bun pm ls loop printed each of them. The fix
sorts the root's entries by name with a stable sort and then collapses
adjacent entries with the same name, so each package is printed once,
while the --trusted filtering in the same branch is unaffected.

<!-- robobun:evidence:end -->
dylan-conway pushed a commit that referenced this pull request Aug 22, 2026
…ites it (#40029)

### Problem
- `bun add` in a project that still has `pnpm-lock.yaml` and
`pnpm-workspace.yaml` crashes or writes garbage into `package.json`.
Sentry BUN-4NQS (1.4.0): `Segmentation fault at address
0xF3E9AD2800000000` in `extend_from_slice` under
`EString::resolve_rope_if_needed`, `print_property`,
`edit_after_resolve`. `bun update -i` hits the same bug (#23694).
- The cause is `update_package_json_after_migration`
(`src/install/pnpm.rs:2334`). It edits the cached root `package.json`
tree in place, replaces `entry.source.contents`, and returns. The tree
now points into the freed contents, and its new nodes live in the
thread-local AST `Store`, which the install resets
(`src/install/lockfile/Package.rs:1676`) before the write-back prints
the tree.

### Fix
- Print the edited tree into the cache entry and call
`MapEntry::reparse_root`, as `store_entry`
(`add_remove_with_filter.rs:280`) does. When the migration returns, the
entry owns its tree again.
- A print or re-parse failure now crashes like the other editors do.
Before, a print failure left the dangling tree in the cache.
- Verified: two new tests in
`test/cli/install/migration/pnpm-lock-v9.test.ts` (`bun add`, and the
`bun update -i` shape from #23694). Both fail on main with the debug
build and with the 1.4.0 release. The other `pnpm-*` files pass.

### Background
- `WorkspacePackageJSONCache` keeps one `MapEntry` per `package.json`:
the contents, a parsed tree, and the arena that owns the tree. `bun add`
prints the root entry again after the install
(`package_json_write_back.rs:63`).
- `Expr::init` allocates nodes in a thread-local `Store`.
`initialize_store()` resets it before every `package.json` parse.
`reparse_root` rebuilds the tree in an arena the entry owns.
- String nodes point into the entry's contents, so a writer that
replaces the contents has to rebuild the tree.

<details><summary>Notes</summary>

- Repro without a registry: a `package.json`, a `pnpm-lock.yaml` with
`lockfileVersion: '9.0'` and empty importers, a `pnpm-workspace.yaml`
with a `packages:` list, then `bun add ./some-folder`. The 1.4.0 release
writes `"\x00\x00\x00e"` in place of the `name` key, so `package.json`
is no longer valid JSON. The debug build reports `heap-use-after-free`
in `EString::eql_bytes` under `edit_after_resolve`, freed at
`pnpm.rs:2759` (the `source.contents` assignment on main). Any yaml
field the migration moves (`packages`, `catalog`, `overrides`,
`patchedDependencies`) or a `pnpm.overrides` block triggers it, so every
pnpm workspace does.
- Why the two builds differ: the debug `Store` reset poisons its blocks
with `0xAA`, so the second print crashes every time. In release, the
freed contents buffer gets a free-list pointer written over its first
bytes (the `name` key), and the `Store` slots are reused by the
install's parse of the root `package.json`, which gives the rope walk in
the Sentry stack.
- Readers of the root entry's tree after the lockfile load, all covered
by the one re-parse: the `bun add` / `bun update <name>` / `bun link`
write-back and `sync_lockfile` (the Sentry crash), `bun update -i`
(#23694), `bun update` with patches (`warn_orphaned_patches`), `bun
dedupe` with an empty root, `bun audit --fix`, and the
`--frozen-lockfile` error message (`overrides_field_name`). Plain `bun
install` only reads `entry.source` after the migration, so it does not
crash.
- The open #38775, #38754, and #38804 each add this same print and
re-parse to `update_package_json_after_migration` as part of larger
behavior changes (all three are in the fold branch #39403). None of them
is in 1.4.0. This PR is only the crash fix, so it can land on its own.
Whichever lands second gets a conflict in this block that resolves to
the same code.
- Not changed here: the `patch --commit` branch in
`update_package_json_and_install_with_manager_with_updates`
(`updatePackageJSONAndInstall.rs:573`) replaces the root entry's
contents the same way without a re-parse. Nothing reads that entry's
tree afterwards today, and #38754 reworks that block. Also separate:
`bun remove` during a pnpm migration writes its pre-install print back
to disk and loses the fields the migration moved. That is a different
bug and is tracked on its own.
- Suites run with the debug build: `pnpm-lock-v9` (84 pass),
`pnpm-migration`, `pnpm-lock-migration`, `pnpm-comprehensive`,
`pnpm-migration-complete` (the last one is a single test that sits near
the 5 s default timeout when run next to other files, unrelated to this
change). `complex-workspace` and `yarn-lock-migration` need network
access and fail here with the release build too.
</details>

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

---

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

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

```console
ASAN without fix: 2 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" "test/cli/install/migration/pnpm-lock-v9.test.ts"
bun test v1.4.1 (4448a2e)

test/cli/install/migration/pnpm-lock-v9.test.ts:
(pass) pnpm-lock.yaml v9 > v9 git and userinfo-tarball references migrate [372.39ms]
(pass) pnpm-lock.yaml v9 > v9 alias in snapshot optionalDependencies gets the npm: prefix [531.51ms]
(pass) pnpm-lock.yaml v9 > reports the missing packages entry of a workspace importer [188.78ms]
(pass) pnpm-lock.yaml v9 > reports the missing packages entry of a transitive dependency [164.22ms]
(pass) pnpm-lock.yaml v9 > reports the missing packages entry [227.90ms]
(pass) pnpm-lock.yaml v9 > reports an importer whose package.json is missing [170.19ms]
(pass) pnpm-lock.yaml v9 > registry-qualified dep path resolves from the configured registry with a warning [258.83ms]
(pass) pnpm-lock.yaml v9 > named registries > built-in npmjs: entries record the npmjs registry [214.16ms]
(pass) pnpm-lock.yaml v9 > named registries > namedRegistries entry pointing at the configured registry needs no warning [445.93ms]
(pass) pnpm-lock.yaml
... (truncated)

release without fix: 2 FAILED
bun test v1.4.0-canary.1 (4448a2e)

test/cli/install/migration/pnpm-lock-v9.test.ts:
(pass) pnpm-lock.yaml v9 > v9 git and userinfo-tarball references migrate [9.21ms]
(pass) pnpm-lock.yaml v9 > v9 alias in snapshot optionalDependencies gets the npm: prefix [49.92ms]
(pass) pnpm-lock.yaml v9 > reports the missing packages entry [5.92ms]
(pass) pnpm-lock.yaml v9 > reports the missing packages entry of a workspace importer [4.76ms]
(pass) pnpm-lock.yaml v9 > reports the missing packages entry of a transitive dependency [3.30ms]
(pass) pnpm-lock.yaml v9 > reports an importer whose package.json is missing [3.40ms]
(pass) pnpm-lock.yaml v9 > registry-qualified dep path resolves from the configured registry with a warning [13.91ms]
(pass) pnpm-lock.yaml v9 > named registries > built-in npmjs: entries record the npmjs registry [9.93ms]
(pass) pnpm-lock.yaml v9 > named registries > namedRegistries entry pointing at the configured registry needs no warning [20.87ms]
(pass) pnpm-lock.yaml v9 > named registries > namedRegistries entry pointing at another registry is used for the tarballs [19.37ms]
(pass) pnpm-lock.yaml v9 > named registries > two packages from one unknown re
... (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/cli/install/migration/pnpm-lock-v9.test.ts"
bun test v1.4.1 (4448a2e)

test/cli/install/migration/pnpm-lock-v9.test.ts:
(pass) pnpm-lock.yaml v9 > v9 git and userinfo-tarball references migrate [309.70ms]
(pass) pnpm-lock.yaml v9 > v9 alias in snapshot optionalDependencies gets the npm: prefix [405.45ms]
(pass) pnpm-lock.yaml v9 > reports the missing packages entry [167.07ms]
(pass) pnpm-lock.yaml v9 > reports the missing packages entry of a workspace importer [174.28ms]
(pass) pnpm-lock.yaml v9 > reports the missing packages entry of a transitive dependency [172.30ms]
(pass) pnpm-lock.yaml v9 > reports an importer whose package.json is missing [157.78ms]
(pass) pnpm-lock.yaml v9 > registry-qualified dep path resolves from the configured registry with a warning [197.44ms]
(pass) pnpm-lock.yaml v9 > named registries > built-in npmjs: entries record the npmjs registry [193.97ms]
(pass) pnpm-lock.yaml v9 > named registries > namedRegistries entry pointing at the configured registry needs no warning [335.05ms]
(pass) pnpm-lock.yaml
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     2a65176
  features     baseline

23 deps, 129 codegen, 1172 objects in 1101ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1244] install /workspace/bun
bun install v1.4.0-canary.1 (4448a2e)

Checked 26 installs across 63 packages (no changes) [7.00ms]
[2/1244] install /workspace/bun/packages/bun-error
bun install v1.4.0-canary.1 (4448a2e)

Checked 1 install across 2 packages (no changes) [2.00ms]
[3/1244] gen ErrorCode+*.h
[4/1244] fetch tinycc
[tinycc] up to date
[5/1243] install /workspace/bun/src/node-fallbacks
bun install v1.4.0-canary.1 (4448a2e)

Checked 111 installs across 104 packages (no changes) [10.00ms]
[6/1243] gen bindgenv2
[7/1243] fetch zlib
[zlib] up to date
[8/1243] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[9/1216] gen node-fallbacks/react-refresh.js
Bundled 1 module in 10ms

  react-refresh.js  4.81 KB  (entry point)

[10/1216] gen .bind.ts → GeneratedBindings.cpp
[11/1216] gen ProcessBindingConstants.lut.h
Generating /workspace/bun/bu
... (truncated)
```

</details>

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

```
src/install/pnpm.rs                             |  37 ++------
 test/cli/install/migration/pnpm-lock-v9.test.ts | 113 ++++++++++++++++++++++++
 2 files changed, 121 insertions(+), 29 deletions(-)
```

</details>

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

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

```
file                                             reads  edits  tests
src/install/pnpm.rs                                  4      3      0
test/cli/install/migration/pnpm-lock-v9.test.ts      3      2      0
```

</details>

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

kjanat commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

@Jarred-Sumner Status on #37150 and its stacked follow-up #37283:

Both predate the Aug 12 cutoff, but overlap substantially with this branch. #37283 currently has 8 conflict hunks against claude/install-mega.

Their core behavior is not covered by the fold:

Several folded PRs overlap with parts of the implementation: #39096 (directories.bin), #38972 (native-binlink retry state), #38797 (libc-aware native-binlink selection), and #37851 (bunx cache/install locking). Those are straightforward to account for in a rebase once the relevant changes have settled.

I’d prefer #37150 and #37283 to remain their existing stack and land separately before the mega branch, rather than folding their remaining behavior into it. That preserves the reviewable boundary between the package-selection fix and the anonymous-URL follow-up.

Also related, but not mine: #36827 (@robobun) is a small case-insensitive .exe invocation-name fix for bunx / node. That one may make more sense to absorb here.

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator Author

This is Claude (an AI assistant) writing on Jarred's behalf.

Included in #44968: merged whole as the first layer; 12 of its PRs were taken out again, each is listed on its own.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants