Skip to content

install: sort workspace deps by resolved name when writing bun.lock - #40809

Merged
Jarred-Sumner merged 3 commits into
mainfrom
farm/2e42f5d6/fix-lockfile-git-dep-sort
Aug 29, 2026
Merged

Jarred-Sumner merged 3 commits into
mainfrom
farm/2e42f5d6/fix-lockfile-git-dep-sort

Conversation

@robobun

@robobun robobun commented Aug 28, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • bun i github:user/repo writes the dependency key at the wrong position in the workspaces section of bun.lock. After bun i formidable handlebars then bun i github:lukeed/clsx, the root dependencies map reads formidable, clsx, handlebars (issue Installing dependency from Github hash leads to incorrect alphabetization in lockfile #40803).
  • The real package name is unknown until the repo is fetched, so the dependency enters the lockfile buffer under its version literal (github:lukeed/clsx) and is sorted there (src/install/lockfile/Package.rs:3056). assign_resolution (src/install/PackageManager/PackageManagerResolution.rs:267) later rewrites the name in place to clsx without a re-sort. write_workspace_deps then iterated the stale buffer order.

Fix

  • write_workspace_deps (src/install/lockfile/bun.lock.rs) now sorts the package's dependency ids by their current name at write time, with the same TreeDepsSortCtx comparator the packages section already uses for this exact reason.
  • The comparator and the name order match the parse-time sort, so a lockfile built through the normal parse path is written byte for byte the same as before. Two cases change: the stale-name case, and yarn.lock migration, which inserted workspace deps in yarn.lock order and now writes them alphabetized. The two migration snapshots are updated.
  • The two existing copies of the TreeDepsSortCtx sort closure are folded into one sort method. No behavior change there.
  • Verified: test/cli/install/bun-install-git-deps.test.ts (new test, fails on stock bun). Also ran bun-lock, lockfile-only, bun-update-lockfile-sync, both frozen-lockfile suites, bun-add, bun-lockb, migrate-bun-lockb-v2, and yarn-lock-migration.

Background

  • A package's dependencies live as a slice into one shared buffer. Re-sorting that buffer after resolution is not safe: dependency ids are raw indices into it and are stored in the tree and the hoisted list. A write-time index sort leaves the buffer alone.
  • The packages section of the writer already builds a sorted id list per package for the same reason, so its output was never affected. Only the workspaces section read the buffer directly.
  • The same holds for any no-alias git:, github:, or tarball-URL add. The new test uses a local git+file:// repo, so it stays hermetic.
Notes
  • The dependency groups (dependencies, devDependencies, optionalDependencies, peerDependencies) are written by an outer loop that filters on behavior, so the write-time sort only needs name order. A name that appears in two groups is split by the filter, as in the packages section.
  • package.json is edited with the resolved name (UpdateRequest::get_resolved_name), so a delete of bun.lock plus a fresh bun i already produced the right order. Only the add path wrote the stale position, and a later no-change install preserved it. This matches the report.
  • Fail-before: USE_SYSTEM_BUN=1 bun test test/cli/install/bun-install-git-deps.test.ts -t "sorts the workspace dependency" fails with iii-middle before hhh-first. Passes with the fix.

[review] gate passed · iteration 1 · 3 files touched

fails on main (without fix)
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/cli/install/bun-install-git-deps.test.ts
bun test v1.4.1 (65362b53b)

test/cli/install/bun-install-git-deps.test.ts:
(pass) installs every github: dependency that appears directly and transitively [1130.30ms]
(pass) installs every tarball-URL dependency that appears directly and transitively [1586.61ms]
(pass) hoisted linker installs the locked commit from a cold cache after the branch moves [1738.20ms]
(pass) installs every git dependency from a lockfile on a cold cache when deps share one repo [1903.67ms]
(pass) installs a git+file:// dependency [487.76ms]
(pass) isolated linker installs the locked commit from a cold cache after the branch moves [1294.06ms]
615 |   expect(second.exitCode).toBe(0);
616 | 
617 |   const lockfile = Bun.JSONC.parse(await Bun.file(join(project, "bun.lock")).text()) as {
618 |     workspaces: Record<string, { dependencies: Record<string, string> }>;
619 |   };
620 |   expect(Object.keys(lockfile.workspaces[""].dependencies)).toEqual(["hhh-first", "iii-middle", "jjj-last"]);
                           
... (truncated)

release without fix: all passed
bun test v1.4.1-canary.1 (8020b6142)

test/cli/install/bun-install-git-deps.test.ts:
(pass) installs every tarball-URL dependency that appears directly and transitively [141.63ms]
(pass) installs every github: dependency that appears directly and transitively [154.75ms]
(pass) bun install <git url> sorts the workspace dependency by its resolved name [285.50ms]
(pass) installs a git+file:// dependency [293.54ms]
(pass) isolated linker installs the locked commit from a cold cache after the branch moves [370.48ms]
(pass) hoisted linker installs the locked commit from a cold cache after the branch moves [423.44ms]
(pass) installs every git dependency from a lockfile on a cold cache when deps share one repo [447.36ms]
(pass) installs every git dependency when many branches of one repo appear directly and transitively [726.87ms]

 8 pass
 0 fail
 10 snapshots, 78 expect() calls
Ran 8 tests across 1 file. [857.00ms]
__F:0:S:0
passes on PR (with fix)
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-install-git-deps.test.ts
bun test v1.4.1 (65362b53b)

test/cli/install/bun-install-git-deps.test.ts:
(pass) installs every github: dependency that appears directly and transitively [734.73ms]
(pass) installs every tarball-URL dependency that appears directly and transitively [984.85ms]
(pass) hoisted linker installs the locked commit from a cold cache after the branch moves [1097.26ms]
(pass) installs every git dependency from a lockfile on a cold cache when deps share one repo [1212.95ms]
(pass) installs every git dependency when many branches of one repo appear directly and transitively [1447.73ms]
(pass) installs a git+file:// dependency [409.96ms]
(pass) isolated linker installs the locked commit from a cold cache after the branch moves [768.63ms]
(pass) bun install <git url> sorts the workspace dependency by its resolved name [466.78ms]

 8 pass
 0 fail
 10 snapshots, 78 expect() calls
Ran 8 tests across 1 file. [4.25s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 667ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/6] gen generated_host_exports.rs
generated_host_exports.rs: 122 exports (host=5, lazy=10, generic=107, rust=0); 243 extern-C blocks audited
[1/6] cargo bun_runtime → libbun_runtime.a
�[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_base64 v0.0.0 (/workspace/bun/src/base64)
�[1m�[92m   Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m   Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_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   Compiling�[0m bun_brotli v0.0.0 (/workspace/bun/src/brotli)
�[1m�[92m   Compiling�[0m
... (truncated)
diff hotspot
src/install/lockfile/bun.lock.rs                   | 61 ++++++++++++----------
 test/cli/install/bun-install-git-deps.test.ts      | 33 ++++++++++++
 .../__snapshots__/yarn-lock-migration.test.ts.snap |  6 +--
 3 files changed, 68 insertions(+), 32 deletions(-)

gate history · 2 passed · 0 rejected · iteration 1

evidence per changed file
file                                                      reads  edits  tests
src/install/lockfile/bun.lock.rs                              6      6      0
test/cli/install/bun-install-git-deps.test.ts                 3      1      0
…igration/__snapshots__/yarn-lock-migration.test.ts.snap      0      0      0

root cause · written by the author bot

When a git or tarball dependency is installed without an alias, the lockfile's workspace dependencies are sorted at parse time under the version literal (for example "github:user/repo"), and assign_resolution later rewrites the entry's name to the actual package name without triggering a re-sort, leaving the entry in the wrong alphabetical position. The fix re-sorts the dependencies by their current names at stringify time, so the lockfile is written using the final resolved names and the entries land in correct alphabetical order.

A git, github, or tarball dependency added with no alias enters the
lockfile buffer under its version literal because the real package name
is unknown until the source is fetched. assign_resolution later rewrites
the name in place without a re-sort, so the workspaces section wrote the
resolved key at the literal's sorted position. Sort the dependency ids
by their current name at write time, the same way the packages section
already does.

Fixes #40803
@coderabbitai

coderabbitai Bot commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 23 days. After that, they cost $0.25 per reviewed file.

Or wait 15 minutes for your next included review.

View limit details

Limit details: You’ve used all 5 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c9d80c3e-5de0-49bb-b87b-06d2497f225d

📥 Commits

Reviewing files that changed from the base of the PR and between dd0e2c2 and f1d027f.

⛔ Files ignored due to path filters (1)
  • test/cli/install/migration/__snapshots__/yarn-lock-migration.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • src/install/lockfile/bun.lock.rs
  • test/cli/install/bun-install-git-deps.test.ts

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

@robobun

robobun commented Aug 28, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 10:00 AM PT - Aug 28th, 2026

❌ @robobun, your commit f1d027f has 1 failures in Build #107801 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 40809

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

bun-40809 --bun

The migration path inserts workspace dependencies in yarn.lock order.
The lockfile writer now sorts them by name, so the snapshots change to
the alphabetized order.
Comment thread src/install/lockfile/bun.lock.rs Outdated
Comment thread src/install/lockfile/bun.lock.rs

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — the write-time index sort mirrors what the packages section already does a few hundred lines up, and the two follow-up commits look to have addressed the earlier inline notes.

What was reviewed:

  • TreeDepsSortCtx::sort extraction is a byte-for-byte fold of the two existing closures; comparator unchanged.
  • write_workspace_deps now collects deps_list.begin()..end() into a fresh Vec<DependencyID> and sorts it — same pattern as pkg_deps_sort_buf at line ~770, so the shared buffer stays untouched and existing dep-id indices remain valid.
  • New test is hermetic (git+file:// via makeSharedRepo, tempDir, bunExe/bunEnv), asserts stderr before exit code, and checks exact key order; yarn.lock migration snapshot reorderings are the expected fallout since that path never went through parse-time sort.
Extended reasoning...

Overview

This PR fixes issue #40803: bun install <git url> without an alias wrote the workspace dependency into bun.lock at the position of its version literal (e.g. git+file://...) rather than its resolved package name, because assign_resolution renames the dep in place after parse-time sorting. The fix adds a write-time index sort over the workspace's dependency-id range in write_workspace_deps (src/install/lockfile/bun.lock.rs), using the same TreeDepsSortCtx comparator the packages section already applies for the same reason. The two existing inline sort closures are folded into a TreeDepsSortCtx::sort() method with no behavior change. A new hermetic test in test/cli/install/bun-install-git-deps.test.ts sets up hhh-first/jjj-last file deps, installs a local bare-repo git dep resolving to iii-middle, and asserts the lockfile's workspace dependencies keys are alphabetical. Two yarn.lock migration snapshots are updated to reflect now-alphabetical workspace dep ordering.

Security risks

None. This is purely lockfile-serialization ordering; no untrusted-input parsing, no network, no auth/crypto. The test uses a local git+file:// repo and never contacts external hosts. The new Vec<DependencyID> is built from a bounded range already used elsewhere in the file (line ~770, ~3118, ~3178) and indexed into deps_buf the same way the pre-existing loop did.

Level of scrutiny

Low-to-moderate. The change is small (~30 net lines in bun.lock.rs), applies an established pattern from the same file to a sibling code path, and the comparator is unchanged so already-sorted lockfiles serialize identically. The only observable output change beyond the bug fix is yarn.lock migration ordering, which the snapshot updates capture and which is arguably a correctness improvement (previously it preserved yarn.lock's arbitrary order). No CODEOWNERS cover the touched paths.

Other factors

The prior run left two inline notes at lines 1332/1334; each was followed by a commit (7b15b9d and f1d027f) before the author resolved the thread, so they were plausibly addressed rather than dismissed — the current code at those lines is clean and matches file conventions. The new test follows harness conventions (tempDir, test.concurrent, bunExe/bunEnv via runInstall, stderr asserted before exit code, exact .toEqual on key order), is appended to the existing git-deps test file rather than a new regression file, and the PR body documents fail-before with USE_SYSTEM_BUN=1. The author also ran the broader lockfile/frozen-lockfile/migration suites. Exit reason was dry_streak, so the hunt ran to completion with no findings.

@robobun

robobun commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

CI state on f1d027f: the new test and all lockfile suites pass. The remaining red lane is test/js/web/url/url.test.ts on darwin x64, which also fails on main and is not related to this change. The other failures passed on retry.

@Jarred-Sumner
Jarred-Sumner merged commit c89fc95 into main Aug 29, 2026
10 of 11 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the farm/2e42f5d6/fix-lockfile-git-dep-sort branch August 29, 2026 05:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants