Skip to content

install: add [install.lockfile] lockfileVersion to cap bun.lock lockfileVersion - #36464

Open
robobun wants to merge 6 commits into
mainfrom
farm/a593ab52/lockfile-format-version-option
Open

robobun wants to merge 6 commits into
mainfrom
farm/a593ab52/lockfile-format-version-option

Conversation

@robobun

@robobun robobun commented Jul 30, 2026 •

Copy link
Copy Markdown
Collaborator

What

Adds a bunfig.toml option to cap the lockfileVersion written to bun.lock:

[install.lockfile]
lockfileVersion = 1

Why

#31539 bumped the default text lockfile to lockfileVersion: 2 for fresh installs and migrations. Bun releases before 1.4 reject version 2 with Unknown lockfile version. #31602 already ensures an existing v1 lockfile is preserved on re-save, and #32465 makes the error actionable going forward, but there is no way to make a fresh install (or a migration from package-lock/yarn/pnpm) produce a lockfile that a teammate on 1.3.x can read.

With lockfileVersion = 1 committed alongside the project:

  • a fresh bun install writes "lockfileVersion": 1
  • an existing v2 lockfile is re-saved as v1 on the next install (the cap below the loaded version is a FORCE_SAVE_LOCKFILE trigger, same as changed_config_version)
  • a loaded v1 lockfile stays at v1 even with lockfileVersion = 2 set (the cap is a ceiling, not a floor)

The v1 format is a strict subset of what the v2 writer emits (v1 to v2 only added parse-time checks on identical content), so capping at v1 never loses information. The existing v0 to v1 floor still applies since the writer cannot emit v0-format workspace entries. Values at or above the current version are a no-op. --frozen-lockfile is unaffected (force_save_lockfile is gated behind do_.save_lockfile(), which frozen-lockfile clears).

The key name matches the bun.lock field and npm's --lockfile-version flag.

Tests

New tests in test/cli/install/lockfile-version-2.test.ts:

bunfig [install.lockfile] lockfileVersion = 1 writes a v1 lockfile for a fresh install
bunfig [install.lockfile] lockfileVersion = 1 downgrades an existing v2 lockfile on install
bunfig [install.lockfile] lockfileVersion = 2 does not bump a loaded v1 lockfile
bunfig [install.lockfile] lockfileVersion = 0 writes lockfileVersion 1 for a fresh install
bunfig [install.lockfile] lockfileVersion = 2 writes lockfileVersion 2 for a fresh install
bunfig [install.lockfile] lockfileVersion = 99 writes lockfileVersion 2 for a fresh install

Fail-before on the first two and the = 0 case; the rest are no-op controls. Full file: 15 pass, 0 fail.

Also adds a Lockfile format versions section to docs/pm/lockfile.mdx and documents the key in docs/runtime/bunfig.mdx.


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

fails on main (without fix)
ASAN without fix: 4 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" "test/cli/install/lockfile-version-2.test.ts"
bun test v1.4.0 (c49791713)

test/cli/install/lockfile-version-2.test.ts:
(pass) a freshly written text lockfile defaults to version 2 [180.28ms]
48 |   });
49 |   const [out, err, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
50 | 
51 |   const lockfile = await file(join(String(dir), "bun.lock")).text();
52 |   expect(err).not.toContain("error:");
53 |   expect(lockfile).toContain(`"lockfileVersion": 1,`);
                        ^
error: expect(received).toContain(expected)

Expected to contain: "\"lockfileVersion\": 1,"
Received: "{\n  \"lockfileVersion\": 2,\n  \"configVersion\": 1,\n  \"workspaces\": {\n    \"\": {\n      \"name\": \"root\",\n      \"dependencies\": {\n        \"dep\": \"file:./dep\",\n      },\n    },\n  },\n  \"packages\": {\n    \"dep\": [\"dep@file:dep\", {}],\n  }\n}\n"

      at <anonymous> (/workspace/bun/test/cli/install/lockfile-version-2.test.ts:53:20)
(fail) bunfig [install.lockfile] lockfileVersion = 1 writes a v1 loc
... (truncated)

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

test/cli/install/lockfile-version-2.test.ts:
(pass) a freshly written text lockfile defaults to version 2 [4.37ms]
(pass) bunfig [install.lockfile] lockfileVersion = 1 writes a v1 lockfile for a fresh install [3.28ms]
(pass) bunfig [install.lockfile] lockfileVersion = 1 downgrades an existing v2 lockfile on install [5.87ms]
(pass) bunfig [install.lockfile] lockfileVersion = 2 does not bump a loaded v1 lockfile [3.09ms]
(pass) bunfig [install.lockfile] lockfileVersion = 0 does not re-save an unchanged v1 lockfile [6.21ms]
(pass) bunfig [install.lockfile] lockfileVersion = 0 writes lockfileVersion 1 for a fresh install [3.22ms]
(pass) bunfig [install.lockfile] lockfileVersion = 2 writes lockfileVersion 2 for a fresh install [2.74ms]
(pass) bunfig [install.lockfile] lockfileVersion = 99 writes lockfileVersion 2 for a fresh install [2.93ms]
(pass) re-saving a v1 lockfile keeps it at version 1 even after adding a dependency [3.69ms]
(pass) re-saving a v0 lockfile floors it to version 1 so it stays parseable [5.91ms]
(pass) an existing v1 lockfile still loads (backward compatible) [2.73ms]
(pass) off-registry npm tarball integrity is 
... (truncated)
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/lockfile-version-2.test.ts"
bun test v1.4.0 (c49791713)

test/cli/install/lockfile-version-2.test.ts:
(pass) a freshly written text lockfile defaults to version 2 [185.33ms]
(pass) bunfig [install.lockfile] lockfileVersion = 1 writes a v1 lockfile for a fresh install [155.74ms]
(pass) bunfig [install.lockfile] lockfileVersion = 1 downgrades an existing v2 lockfile on install [294.55ms]
(pass) bunfig [install.lockfile] lockfileVersion = 2 does not bump a loaded v1 lockfile [163.30ms]
(pass) bunfig [install.lockfile] lockfileVersion = 0 does not re-save an unchanged v1 lockfile [288.56ms]
(pass) bunfig [install.lockfile] lockfileVersion = 0 writes lockfileVersion 1 for a fresh install [160.62ms]
(pass) bunfig [install.lockfile] lockfileVersion = 2 writes lockfileVersion 2 for a fresh install [139.30ms]
(pass) bunfig [install.lockfile] lockfileVersion = 99 writes lockfileVersion 2 for a fresh install [146.33ms]
(pass) re-saving a v1 lockfile keeps it at version 1 even after adding a dependency [160.06ms]
(pass) re-saving
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 897ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[0/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_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   Compiling�[0m bun_output v0.0.0 (/workspace/bun/src/output)
�[1m�[92m   Compiling�[0m bun_clap v0.0.0 (/workspace/bun/src/clap)
�[1m�[92m   Compiling�[0m bun_valkey v0
... (truncated)
diff hotspot
docs/pm/lockfile.mdx                               |  13 ++
 docs/runtime/bunfig.mdx                            |   7 +
 src/bunfig/bunfig.rs                               |   5 +
 .../PackageManager/PackageManagerOptions.rs        |  10 ++
 src/install/PackageManager/install_with_manager.rs |   7 +-
 src/install/lockfile/bun.lock.rs                   |  16 +-
 src/options_types/schema.rs                        |   2 +
 test/cli/install/lockfile-version-2.test.ts        | 170 +++++++++++++++++++++
 8 files changed, 224 insertions(+), 6 deletions(-)

gate history · 3 passed · 0 rejected · iteration 1

evidence per changed file
file                                                 reads  edits  tests
docs/pm/lockfile.mdx                                     1      3      0
docs/runtime/bunfig.mdx                                  1      2      0
src/bunfig/bunfig.rs                                     4      2      0
src/install/PackageManager/PackageManagerOptions.rs      6      6      0
src/install/PackageManager/install_with_manager.rs       5      3      0
src/install/lockfile/bun.lock.rs                         4      5      0
src/options_types/schema.rs                              1      3      0
test/cli/install/lockfile-version-2.test.ts              3      5      0

…eVersion

Bun 1.4 writes lockfileVersion 2 for fresh installs and migrations, which
older Bun versions cannot read. Existing v1 lockfiles are preserved on
re-save, but there was no way to make a fresh install produce v1 for a
project shared with older Bun releases.

Add `[install.lockfile] formatVersion = N` in bunfig.toml. The written
lockfileVersion never exceeds N (floored to 1, since the writer cannot
emit v0 content). An existing v2 lockfile is downgraded on the next
re-save. Unset or >= current version is a no-op.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds install.lockfile.lockfileVersion configuration, propagates it through install options, applies it when writing bun.lock, and covers the behavior with documentation and CLI tests.

Changes

Lockfile version capping

Layer / File(s) Summary
Configuration propagation
src/options_types/schema.rs, src/bunfig/bunfig.rs, src/install/PackageManager/PackageManagerOptions.rs
Adds the optional lockfileVersion setting, parses it, initializes its default, and converts configured values to lockfile versions.
Capped lockfile writing
src/install/PackageManager/install_with_manager.rs, src/install/lockfile/bun.lock.rs
Re-saves text lockfiles when the configured cap is below the loaded version and applies the cap during lockfile version selection.
Behavior validation and documentation
docs/pm/lockfile.mdx, docs/runtime/bunfig.mdx, test/cli/install/lockfile-version-2.test.ts
Documents compatibility behavior and tests fresh v1 writes, v2-to-v1 downgrades, v1 preservation, and boundary values.

Possibly related PRs

  • oven-sh/bun#36458: Both changes update lockfile version compatibility handling in install_with_manager.rs.

Suggested reviewers: jarred-sumner

🚥 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 names the new bunfig lockfileVersion cap and matches the main change.
Description check ✅ Passed The description includes the feature, rationale, and tests; it covers the template's substance despite different headings.

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

Comment thread src/install/PackageManager/PackageManagerOptions.rs Outdated
Comment thread src/install/lockfile/bun.lock.rs Outdated
Comment thread src/options_types/schema.rs Outdated

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

🤖 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 `@docs/pm/lockfile.mdx`:
- Around line 55-58: Correct the lockfile format documentation to state that
re-saving a loaded version 0 lockfile upgrades it to version 1 because the
writer cannot emit v0 workspace entries; only version 1 is preserved on re-save,
while fresh installs and migrations still write version 2.

In `@src/bunfig/bunfig.rs`:
- Around line 1405-1409: Update the formatVersion parsing in the lockfile
configuration flow to accept only finite, non-negative integer numbers before
calling num_to_u32; reject fractional, negative, and non-finite values without
setting install.lockfile_format_version, while preserving handling of valid
integers.

In `@test/cli/install/lockfile-version-2.test.ts`:
- Around line 100-122: Extend the formatVersion test matrix to cover 0 and
assert it produces a v1 lockfile, while retaining the existing v2 and
above-current cases. If the parser rejects invalid numeric values, add
representative invalid variants and assert the corresponding validation
behavior.
- Around line 42-55: Update the install test cases around the spawned process
and lockfile reads to assert the drained subprocess result—especially exitCode
and stderr—before calling file(...).text() for bun.lock. Apply this ordering
consistently to the primary case and the additional lockfile-version cases, so
install failures are reported before artifact-read errors.
🪄 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: 8d77c70d-2c41-4bd3-b31b-2a779cad9096

📥 Commits

Reviewing files that changed from the base of the PR and between c08f665 and 381d3d0.

📒 Files selected for processing (7)
  • docs/pm/lockfile.mdx
  • docs/runtime/bunfig.mdx
  • src/bunfig/bunfig.rs
  • src/install/PackageManager/PackageManagerOptions.rs
  • src/install/lockfile/bun.lock.rs
  • src/options_types/schema.rs
  • test/cli/install/lockfile-version-2.test.ts

Comment thread docs/pm/lockfile.mdx
Comment thread src/bunfig/bunfig.rs Outdated
Comment thread test/cli/install/lockfile-version-2.test.ts
Comment thread test/cli/install/lockfile-version-2.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 bugs found, but this adds a new user-facing bunfig key ([install.lockfile] formatVersion), so a maintainer should sign off on the name/semantics. Also note the three comment-cop inline flags are still open — they look like false positives (the flagged lines are ordinary doc comments, not workaround justifications), but worth a quick look.

What was reviewed:

  • version_to_write cap logic — traced all (loaded, cap) combinations; target = min(loaded, cap) with the v0→v1 floor preserved, and from_int returning None for out-of-range values makes cap≥3 a true no-op.
  • Plumbing through api::BunInstall → Options::load → Stringifier::save matches the existing save_text_lockfile pattern.
  • New tests are hermetic (file: deps only), drain pipes concurrently, and cover fresh-install cap, v2→v1 downgrade, and cap-at/above-current no-ops.
Extended reasoning...

Overview

Adds [install.lockfile] formatVersion = N to bunfig, plumbed through api::BunInstall.lockfile_format_version: Option<u32> → Options.lockfile_format_version: Option<Version> → a new cap parameter on Stringifier::version_to_write. The cap is applied as min(loaded, cap) before the existing v0→v1 floor and the v2 content-safety checks. Docs added to docs/pm/lockfile.mdx and docs/runtime/bunfig.mdx; four new tests in test/cli/install/lockfile-version-2.test.ts.

Security risks

None identified. This only lowers the version stamp written to bun.lock; the v2 parse-time security checks (off-registry integrity, unsafe git .bun-tag) are gated on the reader's parsed version and are unaffected by what the writer stamps. Capping to v1 does not weaken any check that wasn't already bypassable by hand-editing the lockfile version.

Level of scrutiny

Medium. The Rust changes are small and mechanical, and the version_to_write refactor preserves the cap=None behavior byte-for-byte (verified by tracing every branch). But this is new user-facing API surface — the option name, its location under [install.lockfile], and the choice to silently ignore unknown/out-of-range values (rather than error) are design calls that fall under the repo's "API design" review guidance and should get maintainer sign-off.

Other factors

  • Three comment-cop inline comments from github-actions[bot] are outstanding. They flag the new doc comments on the added field/param as "paragraph-long workaround justifications", but on inspection they are ordinary 2–3 line doc strings explaining what the field does — likely bot false positives, but still unaddressed.
  • Version::from_int(99) → None means the comment "Unknown (future) versions cap above CURRENT" is slightly imprecise (it becomes no-cap, not a cap-above-current), though the observable behavior is identical and the test for formatVersion = 99 confirms it.
  • Test coverage is solid: fresh-install cap, v2→v1 downgrade on re-save, and two no-op controls; all use file: deps so no registry contact.

Address review:
- Rename the bunfig key to `lockfileVersion` to match the bun.lock field
  and npm's `--lockfile-version`.
- Treat cap < loaded version as a FORCE_SAVE_LOCKFILE trigger (same
  pattern as changed_config_version), so adding the bunfig option alone
  downgrades an existing v2 lockfile on the next install.
- Trim doc comments flagged by the comment linter.
- Correct docs/pm/lockfile.mdx: v0 is floored to v1, not preserved.
- Add test coverage for cap=0 (floors to v1) and cap=2 on a loaded v1
  lockfile (stays v1, cap is a ceiling not a floor).
Comment thread src/install/PackageManager/install_with_manager.rs Outdated
Comment thread src/install/lockfile/bun.lock.rs Outdated
@robobun robobun changed the title install: add [install.lockfile] formatVersion to cap bun.lock lockfileVersion install: add [install.lockfile] lockfileVersion to cap bun.lock lockfileVersion Jul 30, 2026
Comment thread src/install/PackageManager/install_with_manager.rs
A V0 cap on a loaded V1 lockfile would otherwise trigger FORCE_SAVE_LOCKFILE
on every install while version_to_write floors the output back to V1, so the
re-save writes byte-identical content. Flooring the stored cap keeps the
force-save trigger and the writer consistent.
Comment thread test/cli/install/lockfile-version-2.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.

Implementation looks correct after two review passes (both prior findings addressed in b8ba35a and c497917), but this adds a new documented bunfig key — worth a maintainer glance at the API shape (key name, silent no-op on out-of-range values, ceiling-not-floor semantics).

What was reviewed:

  • Traced version_to_write with cap = None/V1/V2 against loaded = V0/V1/V2/CURRENT — behavior-preserving when unset, floors and ceilings correct.
  • Confirmed from_int(n.max(1)) floors 0→V1 and returns None for >2, so the force-save trigger and writer agree and out-of-range is a no-op.
  • Checked the new force-save arm is gated behind do_.save_lockfile() via the existing should_save_lockfile chain, so --frozen-lockfile is unaffected.
Extended reasoning...

Overview

Adds [install.lockfile] lockfileVersion to bunfig, threaded through schema.rs → bunfig.rs → PackageManagerOptions → install_with_manager (new FORCE_SAVE_LOCKFILE arm) → bun.lock.rs (version_to_write gains a cap parameter). Docs updated in lockfile.mdx and bunfig.mdx. Six new tests in lockfile-version-2.test.ts.

Security risks

None. The option only influences which integer is written to the lockfileVersion field of bun.lock; it does not affect what is installed, fetched, or trusted. Out-of-range values collapse to no-op via from_int returning None.

Level of scrutiny

Medium-high. The production diff is small (~50 lines) and follows the sibling concurrentScripts / changed_config_version patterns exactly, but it is (a) new documented user-facing API surface, and (b) touches the text-lockfile version-selection path that governs cross-Bun-version compatibility of committed lockfiles. REVIEW.md flags API-surface additions as needing maintainer sign-off on shape.

Other factors

Two earlier passes from me caught the V0-cap spurious re-save and a stdout/stderr assertion mixup; both were fixed and now have regression tests. All CodeRabbit and comment-cop threads are resolved. The bug-hunting system found nothing this run. Tests are hermetic (file: deps, loopback servers), fail-before is demonstrated in the PR body, and the = 0 no-resave test now correctly asserts against stderr. The remaining question is purely whether a maintainer is happy with the key name and the lenient-input semantics — not correctness.

@robobun

robobun commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

CI on c497917 is green for this diff. test/cli/install/lockfile-version-2.test.ts passed on every lane. The remaining red is unrelated: worker-transfer-terminate-stress.test.ts SIGABRT on debian asan (pre-existing on main), and five tests that passed on retry or when run alone (setInterval.test.js, test-fastutf8stream-reopen.js, fetch-tls-abortsignal-timeout.test.ts, filesink.test.ts, 08965.test.ts). None touch install/lockfile/bunfig.

Ready for review.

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

Development

Successfully merging this pull request may close these issues.

2 participants