Repository navigation
Vendored re-run on an isolated-linker bun.lockb writes two package records with the same local tarball, so frozen installs on Bun 1.3.9/1.4.2 fail intermittently with EEXIST #861
Description
[agent] Found by the scheduled Bun bug-hunt routine (ledger #306).
Summary
After a project vendors a patch into a workspace bun.lockb, a new dependent of the patched package (a new workspace member, or bun add in a member) makes Bun write a second, nested registry record of the same name@version. Bun does this because the hoisted record is now a local tarball. The advised fix is a vendored re-run. It rewires that nested record to the same .socket/vendor/...tgz path and integrity as the first one. Bun's own writer never produces two package records with an identical tarball resolution. On the isolated linker, both records map to the same node_modules/.bun/left-pad@.socket+vendor+…tgz store dir, and bun install --frozen-lockfile fails about half the time:
EEXIST: File exists: failed to link package: left-pad@.socket/vendor/npm/<uuid>/left-pad-1.3.0.tgz (link)
1 package installed
Failed to install 1 package (exit 1)
scan --mode vendored reports success (applied), and vendor --check exits 0 afterwards. An unfrozen bun install fails the same way and leaves bun.lockb unchanged, so nothing heals it.
Impact
CI breaks nondeterministically on a lock that socket-patch reported as successfully vendored. When the link fails, the member's node_modules/left-pad link is missing. The member only resolves the package because Node's lookup happens to walk up to the root copy.
Repro (Linux, main 6811b4e)
The patch API mock is the mock.py from the bug-hunt probe workflows: left-pad@1.3.0 with a marker prepended to index.js, served on 127.0.0.1:8787.
unset BUN_OPTIONS
API="--api-url http://127.0.0.1:8787 --org o --api-token x"; export SOCKET_PATCH_SERVER_URL=http://127.0.0.1:8787
mkdir -p w/packages/a && cd w && git init -q
echo '{"name":"root","version":"1.0.0","private":true,"workspaces":["packages/*"],"dependencies":{"left-pad":"1.3.0","a":"workspace:*"}}' > package.json
echo '{"name":"a","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > packages/a/package.json
printf '[install]\nsaveTextLockfile = false\n' > bunfig.toml; printf 'node_modules\n' > .gitignore
bun install # Bun 1.4.2: binary bun.lockb, isolated linker (workspace default)
socket-patch scan --mode vendored --yes $API # success
mkdir -p packages/c && echo '{"name":"c","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > packages/c/package.json
bun install # adds a nested registry record c/left-pad@1.3.0 (expected)
socket-patch scan --mode vendored --yes $API # success, "applied": rewires c/left-pad to the same local tarball
bun bun.lockb # yarn dump: two "left-pad@1.3.0" records, identical resolved/integrity
git add -A && git commit -qm v && git clone -q . ../cl && cd ../cl
for i in 1 2 3 4 5 6 7 8; do rm -rf node_modules packages/*/node_modules; BUN_INSTALL_CACHE_DIR=$PWD/.c$i bun install --frozen-lockfile >/dev/null 2>&1; echo $?; done
# about half the runs exit 1 with EEXISTExpected vs actual
- Expected: docs/testing/bun-compatibility.md and CLI_CONTRACT.md promise that a vendored lock frozen-installs the patched bytes from a fresh checkout, and that a re-run picks up newly added registry copies. A re-run should leave a lock Bun can install: one record per resolution, with the nested dependency pointing at the existing tarball record.
- Actual: the re-run duplicates the record, and frozen installs fail intermittently with exit 1.
Matrix (Linux; cold-cache fresh clone; vendored re-run after a new member)
| Lock / linker | Bun 1.2.23 | Bun 1.3.9 | Bun 1.4.2 |
|---|---|---|---|
bun.lockb, isolated |
pass (6/6) | fail (2/4 fresh flows, 2/6 repeat installs) | fail (5/5 fresh flows; 4/8 and 3/6 repeat installs) |
bun.lockb, hoisted |
— | — | pass |
text bun.lock v2, isolated (same two tuples; Bun merges them on load) |
— | — | pass (8/8) |
hosted, same flow, bun.lockb isolated |
pass | pass | pass |
control: the same vendored bun.lockb before the new member |
— | — | pass (8/8) |
macOS/Windows untested (no probe branch this run). There's no release baseline for bisecting, because 4.0.0 refuses vendored bun.lockb.
Suspect code
crates/socket-patch-core/src/vendor/bun_binary.rs:105-118: the for package in matches loop calls lock.set_package(package.id, &staged.rel_tgz, …) on every matching record. Where a record already holds that resolution, the dependency could point at it, or the duplicate could be merged, instead of creating a second record with the same tarball.
Backlog review — 2026-10-08
Priority: P1 → P2. Bun binary-lock EEXIST/install failure is visible and recoverable, rather than silent security success.
Activity
mikolalysenko commentedon Oct 5, 2026
[agent] Triage: priority:p1. Bun (npm-family). Not a duplicate; no open PR covers it. Root cause is the bun.lockb vendored rewire giving two package records one tarball resolution; distinct from the other open bun.lockb issues.
Generated by Claude Code
mikolalysenko commentedon Oct 6, 2026
[agent] Bun bug-hunt run 23 (ledger #306), main
9c43dfc, Linux: this also reproduces whenbun addruns inside an existing member. The issue matrix only covered a new member, so this is a second trigger.EEXIST: failed to link package: left-pad@.socket/vendor/npm/<uuid>/left-pad-1.3.0.tgzThe root cause is the same as the one in the issue body (
bun_binary.rsrewires each matching record to the same tarball), so I'm not filing this separately.Generated by Claude Code
mikolalysenko commentedon Oct 7, 2026
Claim-ID: agent/fix-bun-open-issues. Draft fix PR: #1009 (it covers all open
pm:bunissues).Fold late duplicate bun.lockb records into the vendored tarball (#861)
Add vex discover golden entry for late-dependent fixture (#861)
Reconcile #861 with #784's migrated-lock fixture golden entry
Fold late duplicate bun.lockb records that have dependencies (#861)
Keep folding bun.lockb late duplicates when a lock has unresolved opt…
Merge origin/main into agent/fix-bun-open-issues