Skip to content

fix(bun): accept bun.lock lockfileVersion 2 — bun 1.4 re-versioned an unchanged grammar - #224

Merged
Mikola Lysenko (mikolalysenko) merged 3 commits into
mainfrom
fix/bun-lockfile-version-2
Aug 20, 2026
Merged

fix(bun): accept bun.lock lockfileVersion 2 — bun 1.4 re-versioned an unchanged grammar#224
Mikola Lysenko (mikolalysenko) merged 3 commits into
mainfrom
fix/bun-lockfile-version-2

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Why main is red

The required hosted-e2e check fails on every push (main run 32378974818 and the post-#222 run; every open PR too). The job installs floating npm install -g bun@1, which since bun 1.4.0's release (~2026-08-20) resolves to 1.4.0 — and bun 1.4.0 writes bun.lock with "lockfileVersion": 2. Both of our bun lock version gates refused anything but 1, so the bun_hosted_install_proof leg fails at the rewrite step (redirect_bun_lock_unsupported: "bun.lock lockfileVersion is not 1; re-lock with bun >= 1.3"). 15/16 other legs stayed green; the bun crawler was never affected — only the version gates refuse.

What lockfileVersion 2 actually is

Empirically (docker oven/bun:1.3.14 vs oven/bun:1.4.0, same minimist fixture): the two locks are byte-identical except the version integer"configVersion": 1 is present in both, so it is not new in 1.4.

Bun's 1.4 breaking-changes tracker (oven-sh/bun#28792, PR #31539) confirms the bump gates stricter parse-time checks over an unchanged emitted grammar:

"bun.lock default lockfileVersion is now 2. v2 lockfiles require integrity hashes for off-registry npm tarballs and reject unsafe git .bun-tag values at parse time. Existing v0/v1 lockfiles continue to load. Older Bun versions cannot read v2 lockfiles."

Both new constraints are compat-neutral for us: our hosted/vendored URL 3-tuples always carry a sha512 (satisfying the off-registry-integrity rule by construction), and we never emit or edit git entries.

The fix

One shared gate, widened from {1} to {1, 2} (fail-closed on everything else, message updated to "not 1 or 2"):

  • crates/socket-patch-core/src/vendor/bun_lock_text.rsSUPPORTED_LOCK_VERSIONS / check_lock_version, the single production gate, with three call sites:
    • crates/socket-patch-core/src/patch/redirect/mod.rs (rewrite_bun_lockredirect_bun_lock_unsupported)
    • crates/socket-patch-core/src/vendor/bun_lock.rs (vendor backend → vendor_lockfile_version_unsupported)
    • crates/socket-patch-core/src/vendor/lock_inventory.rs (inventory_bun — auto-benefits; now test-covered)
  • Plus a fourth v1-only assumption found while proving the fix: e2e_vendor_bun_build.rs's fixture precondition asserted lockfileVersion: 1 and failed under real bun 1.4.0 — now accepts 1 or 2.
  • Docs: CLI_CONTRACT.md + docs/ecosystems.md bun paragraphs.

Tests (RED-first, verified by reverting the gate to v1-only)

With the production gate temporarily re-pinned to [1], all the new legs fail (v2 refused): the bun_lock_text gate unit, bun_lock::lock_v2_vendors_like_v1, the lock-inventory v2 leg, redirect::tests::bun_lock_warning_branches, and redirect_golden on the new fixture. With the widened gate, everything is green:

  • Gate unit: 1 and 2 accepted; 0/3/99 and missing/non-integer/string heads refused with version + remedy named.
  • Redirect: unit v2 accept + v3 refusal ("not 1 or 2"); in_process_redirect adds scan_redirect_rewrites_bun_lock_v2 (version line preserved verbatim) and scan_redirect_refuses_bun_lock_v3 (fail-closed: lock byte-identical, redirected: 0, warning in the --json envelope).
  • Vendor: lock_v2_vendors_like_v1 + the existing refusal test re-pinned to version 3; inventory reads v1/v2 identically and yields nothing on v3.
  • Golden fixtures: new npm/bun/lock-v2 case; the existing lock-version-unsupported input re-pinned 2 → 3 (2 is now a supported version, the refusal case needs a future one). No existing expected/ bytes were re-blessed. Loud note: these fixtures are the shared cross-language contract — the new lock-v2 case was authored on the Rust side, so the depscan TS twin (golden.test.ts + its bun rewriter gate) needs the matching v2 acceptance and a fixture sync, same as the gem.ts port.

Proof against the real thing

With real bun 1.4.0 on PATH (locally installed, same version CI's bun@1 resolves):

  • e2e_redirect_bun_build: all legs run un-skipped — the main leg now exercises a native v2 lock end-to-end (real bun install, hosted rewrite, fresh-checkout bun install --frozen-lockfile materializes the patched bytes; tampered-tarball twin still refuses). New lock-v2 leg pins the forced-version path and asserts the version line survives the rewrite verbatim.
  • e2e_vendor_bun_build: full vendor → frozen install → revert capstone green on a native v2 lock.
  • The exact red CI leg: cargo test -p socket-patch-cli --test e2e_hosted_production bun_hosted_install_proof -- --ignored against real production — green (3.12s).

Gates: cargo test -p socket-patch-core --lib (2540), redirect_golden, in_process_redirect (43), cargo clippy --workspace --all-features -- -D warnings clean, touched files fmt --check clean (the repo has pre-existing fmt drift in untouched files; CI has no fmt gate).

Deliberately NOT changed

CI's floating npm install -g bun@1 stays floating: it caught this drift within a day of the release — that is precisely the hosted suite's job.

Absorbed: #225 (Windows warning fix)

Main is red on TWO independent required checks — hosted-e2e (bun 1.4 lockfileVersion, fixed here) and test (windows-latest) (#222's {value:?} Debug-escaping in the gem_bundle_config_path_ignored warning, fixed in #225). Every open PR inherits whichever failure it doesn't itself fix, so the two single-purpose PRs deadlocked: each was red on exactly the check the other cures. This PR now carries #225's one-file commit (Display-in-quotes + a platform-independent backslash pin) so one PR can go green on both required checks; #225 is closed as absorbed.

🤖 Generated with Claude Code


Note

Medium Risk
Touches lockfile surgery and integrity-pinning for bun hosted/vendor paths, but only widens an existing fail-closed version gate over an empirically unchanged grammar. Unsupported versions still refuse with the lock left byte-identical.

Overview
Accepts bun 1.4's "lockfileVersion": 2 for hosted redirect, vendor, and lock inventory. Bun 1.4 re-versions the same emitted grammar as v1 (stricter parse checks only); the shared check_lock_version gate now allows {1, 2} and still fail-closes on anything else, preserving the version line verbatim.

This unblocks CI that installs floating bun@1 (now 1.4) and was refusing rewrites with redirect_bun_lock_unsupported. Tests cover v2 rewrite/vendor/inventory, v3 refusal, a new lock-v2 golden, and e2e fresh-checkout install of patched bytes. Docs (CLI_CONTRACT.md, ecosystems.md) match.

Reviewed by Cursor Bugbot for commit 42a4b55. Configure here.

… unchanged grammar

bun 1.4.0 bumped the default bun.lock lockfileVersion to 2 (oven-sh/bun
PR #31539). The bump gates stricter PARSE checks — integrity hashes
required for off-registry npm tarballs, unsafe git .bun-tag values
rejected — behind an UNCHANGED emitted grammar: a 1.3.14 and a 1.4.0
lock of the same fixture are byte-identical except the integer
(verified empirically in docker). Our hosted/vendored tuples always
carry a sha512, satisfying the new off-registry-integrity rule by
construction.

CI's hosted-e2e job installs floating bun@1, which now resolves to
1.4.0 — the shared version gate (check_lock_version in bun_lock_text)
refused everything but 1, so bun_hosted_install_proof went red on every
push to main. Widen the gate to {1, 2}; refuse anything else with the
updated fail-closed message. The floating bun@1 is deliberately kept:
catching exactly this drift is the hosted suite's job.

Covered call sites: redirect rewriter (redirect_bun_lock_unsupported),
vendor backend (vendor_lockfile_version_unsupported), lock inventory,
plus a v1-only fixture assertion in the vendor bun e2e capstone.

New golden fixture npm/bun/lock-v2 (Rust-authored — the depscan TS twin
needs the matching acceptance + fixture sync); the existing
lock-version-unsupported input re-pinned from 2 to 3 (2 is now
supported). No existing expected/ bytes re-blessed.

Proofs, all with real bun 1.4.0 on PATH: redirect + vendor e2e
capstones green on NATIVE v2 locks (fresh-checkout frozen installs of
patched bytes, tamper refusal), and the exact red CI leg
bun_hosted_install_proof green against real production.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… warning

The gem_bundle_config_path_ignored detail interpolated the refused config
value with `{value:?}` (Debug), which escapes backslashes. On Unix the two
forms coincide (temp paths carry no backslashes), but on Windows the
warning printed `C:\\Users\\…` for a config that says `C:\Users\…` —
failing all three in_process_gem_config_warning tests on the
windows-latest CI leg (red on main since #222 merged, inherited by every
open PR) and mangling the path for any human copy-pasting it out of the
warning.

Format with Display inside manual quotes instead. The value is a single
scraped config line, so Display cannot smuggle in newlines the quotes
would mask. New platform-independent unit test pins it with a
backslash-bearing value: `{:?}` doubles backslashes on every OS, so the
pin is RED under the old format everywhere, not just on Windows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…est matrix

`go install vexctl` compiles sigstore/cosign, whose module verification
reads dozens of sum.golang.org checksum tiles. Transient HTTP/2
INTERNAL_ERROR stream resets there failed the macos-latest test leg twice
on this PR (~18 min apart, different modules each time — an upstream
incident, not one bad tile), with the test step never reached. Retry up
to 5 times with growing backoff; a persistent outage still fails loudly
on the last attempt. Follow-up option if it recurs: install the pinned
release binary (sha256-pinned) instead of compiling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Third commit: CI hardening for the flake that kept the macOS leg red — go install vexctl failed twice (~18 min apart) on sum.golang.org HTTP/2 tile resets during module verification, different modules each time, with the test step never reached. The install step now retries 5× with backoff. (Follow-up option if the incident class recurs: sha256-pinned release binary instead of compiling — also saves ~100s per leg.)

@mikolalysenko
Mikola Lysenko (mikolalysenko) merged commit 2e98d67 into main Aug 20, 2026
63 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the fix/bun-lockfile-version-2 branch August 20, 2026 19:43
Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Aug 20, 2026
…on 2)

Unions in scan/mod.rs (gem config-path warning adapted to the new
unsupported_layout_warnings signature) and e2e_redirect_bun_build.rs
(bun_hosted_project carries BOTH the get/scan driver param and #224's
force_lock_version; both new tests kept).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants