[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
The npm crawler only looks for pnpm's virtual store at node_modules/.pnpm (or a pnpm ≤3 node_modules/.<registry-host>). pnpm lets you move it:
virtualStoreDir / virtual-store-dir, supported on every major. For example .vstore, or node_modules/.custom.
- the global virtual store (
enableGlobalVirtualStore, pnpm 10.12+), where entries live under <store>/v10|v11/links/....
pnpm records the location in node_modules/.modules.yaml ("virtualStoreDir": "..."). With either setting, every transitive dependency is invisible to agent mode. apply skips the patch with package_not_installed ("No installed package matches this PURL"), even though Node loads the package from that directory. Direct dependencies are still reachable through their node_modules/<name> symlink. With the global virtual store, reaching them that way causes #361.
The failure is loud (partialFailure), but agent mode simply can't patch transitive deps in these layouts. The reason it gives is wrong ("not installed"), and the docs don't mention the limitation (docs/ecosystems.md, "npm: which node_modules trees are crawled"). Commands that treat "not installed" as a signal (scan --prune / --sync, remove, repair, vex) inherit the blind spot.
Repro
mkdir p && cd p
echo '{"name":"p","version":"0.0.0","private":true,"dependencies":{"is-odd":"3.0.1"}}' > package.json # is-odd -> [email protected] (transitive)
printf 'virtualStoreDir: .vstore\n' > pnpm-workspace.yaml # pnpm <=10 also: printf 'virtual-store-dir=.vstore\n' > .npmrc
pnpm install
ls .vstore/[email protected]/node_modules/is-number/index.js
# hand-stage .socket/manifest.json + blobs for pkg:npm/[email protected] (before = that file)
socket-patch apply --offline --json
# "status": "partialFailure", "action": "skipped", "reason": "No installed package matches this PURL", "errorCode": "package_not_installed"
node -e "console.log(require('fs').readFileSync(require.resolve('is-number',{paths:[require('path').dirname(require.resolve('is-odd'))]}),'utf8').slice(0,18))"
# still the original bytes; the same project without virtualStoreDir -> applied, node sees the patch
For the global virtual store, replace the workspace line with enableGlobalVirtualStore: true. is-number then lives only under <store>/v11/links/@/is-number/6.0.0/<hash>/node_modules/is-number, with the same package_not_installed result.
Expected vs actual
- Expected: agent mode patches every installed copy that Node resolves (docs/ecosystems.md npm row: "any install layout ... every store copy"), locating the virtual store from
node_modules/.modules.yaml. At minimum it should say the store lives elsewhere, not "not installed".
- Actual: skipped as not installed.
Matrix (current main f6b7fb9; default = no virtualStoreDir setting, always passes)
| OS |
pnpm |
custom virtualStoreDir |
global virtual store |
| Linux |
8.15.9 |
fail |
n/a |
| Linux |
9.15.9 |
fail |
n/a |
| Linux |
10.34.5 |
fail |
fail (sandbox); not engaged on GH runner (pnpm 10 ignores the global virtual store under CI) |
| Linux |
11.27.0 / 12.8.1 |
fail |
fail |
| macOS (GH runner) |
10.34.5 / 11.27.0 / 12.8.1 |
fail |
fail on 11 / 12 (10: not engaged) |
| Windows (GH runner) |
10.34.5 / 11.27.0 / 12.8.1 |
fail |
fail on 11 / 12 (10: not engaged) |
Release 4.0.0 behaves the same.
Suspect code
crates/socket-patch-core/src/crawlers/npm_crawler.rs:1033 and :1362: .pnpm is matched by name only, and node_modules/.modules.yaml's virtualStoreDir is never read.
Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36759597534
[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
The npm crawler only looks for pnpm's virtual store at
node_modules/.pnpm(or a pnpm ≤3node_modules/.<registry-host>). pnpm lets you move it:virtualStoreDir/virtual-store-dir, supported on every major. For example.vstore, ornode_modules/.custom.enableGlobalVirtualStore, pnpm 10.12+), where entries live under<store>/v10|v11/links/....pnpm records the location in
node_modules/.modules.yaml("virtualStoreDir": "..."). With either setting, every transitive dependency is invisible to agent mode.applyskips the patch withpackage_not_installed("No installed package matches this PURL"), even though Node loads the package from that directory. Direct dependencies are still reachable through theirnode_modules/<name>symlink. With the global virtual store, reaching them that way causes #361.The failure is loud (
partialFailure), but agent mode simply can't patch transitive deps in these layouts. The reason it gives is wrong ("not installed"), and the docs don't mention the limitation (docs/ecosystems.md, "npm: which node_modules trees are crawled"). Commands that treat "not installed" as a signal (scan --prune/--sync,remove,repair,vex) inherit the blind spot.Repro
For the global virtual store, replace the workspace line with
enableGlobalVirtualStore: true. is-number then lives only under<store>/v11/links/@/is-number/6.0.0/<hash>/node_modules/is-number, with the samepackage_not_installedresult.Expected vs actual
node_modules/.modules.yaml. At minimum it should say the store lives elsewhere, not "not installed".Matrix (current main f6b7fb9;
default= no virtualStoreDir setting, always passes)CI)Release 4.0.0 behaves the same.
Suspect code
crates/socket-patch-core/src/crawlers/npm_crawler.rs:1033and:1362:.pnpmis matched by name only, andnode_modules/.modules.yaml'svirtualStoreDiris never read.Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36759597534