[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, then bun install, then vex refuses all patches with exit 1. A CI job that runs vex on a warm or persisted node_modules fails, and so does a local pre-commit check. Following the advice (re-run the install) doesn't help. Only rm -rf node_modules does.
- 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@version store 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 1
Output 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:
vex attests 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, entry node_modules link or .bun/node_modules hoist link points to isn't an installed copy. The npm/pnpm store walks have the same property, because pnpm prunes its .pnpm entries on install.
- Actual: unreachable
.bun entries 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:1443 and :2132 (list_pnpm_shaped_store_entries_sync) enumerate every .bun entry dir as a package location, with no reachability check.
crates/socket-patch-core/src/vex/verify.rs:211-224 takes 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.
[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 runsbun install --frozen-lockfilein the same checkout. Bun then adds new entries namedis-number@http+++…patch+npm+is-number+6.0.0+…and re-links every importer to them. The oldis-number@6.0.0registry entry stays on disk, but nothing links to it. Nothing can load it any more.vexcounts that orphan as an installed copy, finds the original bytes, and drops every patch from the document asnot_applied. It exits 1, even though every copy the runtime can load is patched. A fresh clone with an emptynode_modulesattests 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_syncwarnings ("re-run your package manager's install to resync it"). Re-running the install can't clear them, because Bun keeps the orphans.Impact
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.name@versionstore dir behind.Repro (Linux, real Bun, local patch-API mock)
Output on main
203e092, hosted, Bun 1.4.2:rm -rf node_modules && bun install --frozen-lockfile, thenvex→ exit 0, 3 verified. Vendored, Bun 1.4.2:vexexit 0 with 3 verified, plus 2vendored_tree_out_of_syncwarnings 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/packageand tarball routes, plus a registry passthrough viaSOCKET_NPM_REGISTRY. The patches append a marker line toindex.js.Expected vs actual
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..bunentries are treated as installed copies. Hosted →not_applied(exit 1). Vendored →vendored_tree_out_of_sync.OS × version
vexafter in-place frozen installlinker = "isolated")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 ownnode_modules/.bun/node_moduleshoist dir and the importer links name exactly the live entries.