Repository navigation
With Bun's isolated linker, vex refuses every hosted patch as not_applied after the usual in-place bun install, because it checks orphaned node_modules/.bun registry entries that Bun never removes (regression from #496) #599
Description
[agent] Found by the scheduled Bun bug-hunt routine (ledger #306).
Summary
Since #496 (commit 35de754, the fix for #366 / #405), the npm crawler walks node_modules/.bun/<name>@<version>… store entries. Bun's isolated linker never prunes that store. After a hosted scan, the developer runs bun install --frozen-lockfile in the same checkout. Bun then adds new entries named is-number@http+++…patch+npm+is-number+6.0.0+… and re-links every importer to them. The old is-number@6.0.0 registry entry stays on disk, but nothing links to it. Nothing can load it any more.
vex counts that orphan as an installed copy, finds the original bytes, and drops every patch from the document as not_applied. It exits 1, even though every copy the runtime can load is patched. A fresh clone with an empty node_modules attests correctly, so only the in-place workflow is affected, and that's the one a developer uses locally.
In vendored mode the same orphans produce false vendored_tree_out_of_sync warnings ("re-run your package manager's install to resync it"). Re-running the install can't clear them, because Bun keeps the orphans.
Impact
- Hosted + isolated linker (the default for a fresh Bun ≥ 1.3 workspace):
scan, thenbun install, thenvexrefuses all patches with exit 1. A CI job that runsvexon a warm or persistednode_modulesfails, and so does a local pre-commit check. Following the advice (re-run the install) doesn't help. Onlyrm -rf node_modulesdoes. - Vendored: the attestation is right, but you get a spurious out-of-sync warning for each patch.
- The same orphan state comes from any earlier version churn: a removed dependency, or an upgrade that leaves an old
name@versionstore dir behind.
Repro (Linux, real Bun, local patch-API mock)
# usage: repro-orphan.sh <bun> <dir> <hosted|vendored>
B=$1; D=$2; M=$3
rm -rf "$D"; mkdir -p "$D/packages/a"; cd "$D"
printf '{"name":"root","version":"1.0.0","private":true,"workspaces":["packages/*"],"dependencies":{"is-odd":"3.0.1"}}' > package.json
printf '{"name":"a","version":"1.0.0","dependencies":{"is-number":"6.0.0","left-pad":"1.3.0"}}' > packages/a/package.json
printf '[install]\nlinker = "isolated"\n' > bunfig.toml
"$B" install # node_modules/.bun/is-number@6.0.0 ...
socket-patch scan --mode "$M" --json # exit 0, bun.lock rewired
"$B" install --frozen-lockfile # same checkout
ls node_modules/.bun # is-number@6.0.0 AND is-number@http+++… (orphan kept)
node -p "require.resolve('is-number',{paths:[require('fs').realpathSync('packages/a')]})" # -> the patched http+++ entry
socket-patch vex --product pkg:npm/root@1.0.0 --output vex.json --json # exit 1Output on main 203e092, hosted, Bun 1.4.2:
store entries: is-number@6.0.0 is-number@http+++127.0.0.1+18999+patch+npm+is-number+6.0.0+… is-odd@3.0.1 is-odd@http+++… left-pad@1.3.0 left-pad@http+++… node_modules
runtime is-number -> is-number@http+++…/node_modules/is-number/index.js (patched)
vex exit=1 error 0 verified; [('pkg:npm/is-number@6.0.0','not_applied'), ('pkg:npm/is-odd@3.0.1','not_applied'), ('pkg:npm/left-pad@1.3.0','not_applied')]
rm -rf node_modules && bun install --frozen-lockfile, then vex → exit 0, 3 verified. Vendored, Bun 1.4.2: vex exit 0 with 3 verified, plus 2 vendored_tree_out_of_sync warnings although the tree is in sync.
The org-scoped API was mocked locally (the sandbox can't reach patches-api). The mock serves the batch, view, patches/package and tarball routes, plus a registry passthrough via SOCKET_NPM_REGISTRY. The patches append a marker line to index.js.
Expected vs actual
- Expected:
vexattests a patch when every copy the install can actually load is patched. Today's code already does this for a fresh clone, and for the hoisted linker, where Bun replaces the dir in place. A store entry that no importer, entrynode_moduleslink or.bun/node_moduleshoist link points to isn't an installed copy. The npm/pnpm store walks have the same property, because pnpm prunes its.pnpmentries on install. - Actual: unreachable
.bunentries are treated as installed copies. Hosted →not_applied(exit 1). Vendored →vendored_tree_out_of_sync.
OS × version
| OS | Bun | linker | hosted vex after in-place frozen install |
vendored |
|---|---|---|---|---|
| Linux | 1.4.2 | isolated (workspace default) | fail (2/2 runs) | false out-of-sync warnings |
| Linux | 1.3.14 | isolated (workspace default) | fail | untested |
| Linux | 1.2.23 | isolated (linker = "isolated") |
fail | untested |
| Linux | 1.4.2 | hoisted | pass (no store) | pass |
| Linux | any | isolated, fresh clone | pass | pass |
| macOS / Windows | — | — | untested (probe branches currently blocked) | — |
First bad commit
35de754 (#496). Before it, the crawler never looked into .bun, which was #405 (the opposite failure: attesting without checking the bytes). Release 4.0.0 predates it.
Suspect code
crates/socket-patch-core/src/crawlers/npm_crawler.rs:1443and:2132(list_pnpm_shaped_store_entries_sync) enumerate every.bunentry dir as a package location, with no reachability check.crates/socket-patch-core/src/vex/verify.rs:211-224takes every crawled copy as an installed copy (package_copies) for the not_applied / out-of-sync verdicts.
Possible directions: for StoreLayout::Bun, count only entries reachable from an importer or the hoist links. Or, when a lock-wired hosted/vendored entry exists, ignore an orphaned registry-named entry. Bun's own node_modules/.bun/node_modules hoist dir and the importer links name exactly the live entries.
Backlog review — 2026-10-08
Priority: P1 → P2. Orphaned Bun store copies make VEX reject an actually patched install. This is a false negative, not an unsafe positive attestation.
Activity
bun: run 9 ledger (#599), pnpm handover
mikolalysenko commentedon Oct 2, 2026
[agent] Triaged as
priority:p1(Bun, npm family). It isn't a duplicate, and I found no open PR for it. The cause: when the npm crawler walks a Bun store (list_pnpm_shaped_store_entries_sync), it counts every.bunentry as an installed copy, with no reachability check.vex/verify.rsthen judges orphaned entries as if they were live copies. This is the opposite direction of #601/#603, which are about copies being missed, so I'm not clustering it with them. One caution for whoever fixes it: agent-modeapplyalso fans out to every.bunentry, so a reachability filter has to treat apply and vex the same way.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).Skip orphaned Bun store entries as installed copies (#599)
Keep orphaned Bun store entries for rollback, seed members from Bun (#…
Reconcile #599 with #635's Bun global store links
Judge orphaned Bun global store links by reachability (#599)
mikolalysenko commentedon Oct 8, 2026
[agent] Bun bug-hunt (ledger #306), main
9472be4, Linux. The same root cause (everynode_modules/.bun/<name>@<ver>entry counts as installed, reachable or not) causes three more symptoms. None of them needs a hosted rewire first. An ordinary version bump or removal is enough:scanexits 1.scan --mode vendored→partial_failure,vendor_lock_entry_not_found: "bun.lock has no packages entry resolving minimist@1.2.2 — make sure the package is installed and locked (bun install) before vendoring" (bun.lockb: "bun.lockb has no registry entry for minimist@1.2.2"). Re-runningbun install(plain or--frozen-lockfile) keeps the orphan, so the advice is a no-op and the scan stays exit 1 untilrm -rf node_modules. Reproduced on Bun 1.3.9 and 1.4.2 × text / lockb × upgrade / remove (8/8 cells). The hoisted linker control exits 0.scanreports a non-existent package. It exits 0, butredirect.patches[](new in Stop CI gates passing on missing paths, unverified agent patches and unreported hosted pins #1029) carries{"purl":"pkg:npm/minimist@1.2.2","action":"unpinned","errorCode":"redirect_unconfirmed"}plus aredirect_bun_entry_not_foundwarning for a version the lock no longer has (same 8 cells).scan --mode agent→success. It patchesnode_modules/.bun/minimist@1.2.2, which nothing can load, and recordspkg:npm/minimist@1.2.2in the manifest.apply --checkthen says "Patches are in sync", andvexemitsnot_affectedfor subcomponentpkg:npm/minimist@1.2.2ofpkg:npm/proj@1.0.0, a version the product doesn't contain (1.4.2).A reachability or lock-membership filter on
.bunentries would cover all three, alongside thevexcase above.Generated by Claude Code
bun: run 32 ledger (#1132, #599 comment, npm handover)
Trim per-entry work in the Bun scan paths
Speed up the Bun isolated orphan walk