[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).
Summary
With Yarn 4's nodeLinker: pnpm, a transitive dependency exists only inside node_modules/.store/<name>-npm-<version>-<hash>/package/. The only path to it is the store entry's node_modules/<name> symlink (-> ../package). Agent-mode apply never finds that copy. It reports skipped / package_not_installed and leaves the package unpatched. When the same run patches other (direct) packages, it ends success with exit 0.
#359 was fixed on main by #365 (61cfb9b, "Find packages in npm .store and pnpm virtualStoreDir"), which now walks node_modules/.store. That fix covers npm's linked layout, where .store/<name>@<v>-<hash>/node_modules/<name> is a real directory. Yarn 4 uses the same .store name, but there the node_modules/<name> child is a symlink to the sibling package/ dir, so the store-entry probe rejects it.
Yarn 3.x's pnpm linker writes real dirs at .store/<slug>/node_modules/<name> and isn't affected.
Impact
Every transitive dependency of a Yarn 4 project on the pnpm linker is unpatchable in agent mode, and when other patches apply the run gives no failure signal (exit 0). The docs say this layout is supported:
- docs/ecosystems.md: "Inside each
node_modules, the package stores of isolated layouts are walked too, since they are the only home of transitive dependencies: … npm's install-strategy=linked store node_modules/.store."
- docs/testing/yarn-berry-compatibility.md: the node-modules and pnpm linkers "are covered end to end". The pnpm-linker e2e (
e2e_yarn4_pnpm_linker_build) only patches a direct dependency.
Repro (Linux, Node 22, yarn 4.12.0)
mkdir p && cd p
echo '{"name":"p","version":"1.0.0","dependencies":{"is-odd":"3.0.1","left-pad":"1.3.0"}}' > package.json
printf 'nodeLinker: pnpm\n' > .yarnrc.yml && touch yarn.lock
yarn install
ls -l node_modules/.store/is-number-npm-6.0.0-*/node_modules/ # is-number -> ../package
# Stage an agent-mode manifest (.socket/manifest.json + .socket/blobs) with two patches:
# pkg:npm/is-number@6.0.0 (transitive, via is-odd) file package/index.js
# pkg:npm/left-pad@1.3.0 (direct) file package/index.js
# (after blob = "/* SOCKET-PATCHED */\n" + original index.js; git-sha256 hashes)
SOCKET_NO_CONFIG=1 socket-patch apply --json; echo "exit=$?"
head -c 20 node_modules/.store/is-number-npm-6.0.0-*/package/index.js
Actual (main 61cfb9b):
status: success summary: {"applied": 1, "skipped": 1, "failed": 0, …}
{"action":"applied","purl":"pkg:npm/left-pad@1.3.0","files":[{"path":"package/index.js","verified":true,"appliedVia":"blob"}]}
{"action":"skipped","purl":"pkg:npm/is-number@6.0.0","reason":"No installed package matches this PURL","errorCode":"package_not_installed"}
exit=0
/*!\n * is-number <- unpatched
With only the is-number patch staged, the run is partialFailure / exit 1 with the same package_not_installed skip.
Expected vs actual
- Expected: the store copy at
.store/is-number-npm-6.0.0-<hash>/package is found and patched, as the docs above promise for isolated-store transitive dependencies (and as already happens for npm's .store, pnpm's .pnpm and Yarn 3's pnpm linker).
- Actual:
package_not_installed, file untouched, exit 0 when anything else applied.
OS × version
| OS |
yarn |
linker |
transitive dep in .store |
| Linux |
3.8.7 |
pnpm |
pass (real dir in the store entry) |
| Linux |
4.0.2 |
pnpm |
fail |
| Linux |
4.12.0 |
pnpm |
fail (reproduced twice, plus a scoped direct dep as control: patched) |
| Linux |
4.18.1 |
pnpm |
fail |
| Linux |
4.12.0 |
node-modules |
pass (control: hoisted node_modules/is-number patched) |
macOS and Windows weren't probed. The layout is yarn's own (symlinks on POSIX, junctions on Windows), so they're likely affected the same way.
First bad version
Not a regression. Release 4.0.0 (npm @socketsecurity/socket-patch) behaves identically on the same tree. 61cfb9b didn't extend .store support to Yarn's layout.
Suspect code
crates/socket-patch-core/src/crawlers/npm_crawler.rs:1140: in visit_resolver_dir, a store-entry node_modules rejects any target that isn't a real directory (is_real_package_dir_sync, npm_crawler.rs:2361, uses symlink_metadata(..).is_dir()). Yarn's node_modules/<name> -> ../package is a symlink to the entry's own package/ dir, which is the only physical copy. Nothing walks <entry>/package directly.
list_npm_store_entries_sync / decode_npm_store_entry_name (npm_crawler.rs:231, ~1951) parse npm's <name>@<version>-<hash> key. Yarn's keys are <slug>-npm-<version>-<hash> (scoped: @scope-name-npm-…), so the advertised name/version is never decoded for them either.
No probe runs yet. macOS/Windows are on the routine's backlog.
[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).
Summary
With Yarn 4's
nodeLinker: pnpm, a transitive dependency exists only insidenode_modules/.store/<name>-npm-<version>-<hash>/package/. The only path to it is the store entry'snode_modules/<name>symlink (-> ../package). Agent-modeapplynever finds that copy. It reportsskipped/package_not_installedand leaves the package unpatched. When the same run patches other (direct) packages, it endssuccesswith exit 0.#359 was fixed on main by #365 (61cfb9b, "Find packages in npm .store and pnpm virtualStoreDir"), which now walks
node_modules/.store. That fix covers npm's linked layout, where.store/<name>@<v>-<hash>/node_modules/<name>is a real directory. Yarn 4 uses the same.storename, but there thenode_modules/<name>child is a symlink to the siblingpackage/dir, so the store-entry probe rejects it.Yarn 3.x's pnpm linker writes real dirs at
.store/<slug>/node_modules/<name>and isn't affected.Impact
Every transitive dependency of a Yarn 4 project on the pnpm linker is unpatchable in agent mode, and when other patches apply the run gives no failure signal (exit 0). The docs say this layout is supported:
node_modules, the package stores of isolated layouts are walked too, since they are the only home of transitive dependencies: … npm'sinstall-strategy=linkedstorenode_modules/.store."e2e_yarn4_pnpm_linker_build) only patches a direct dependency.Repro (Linux, Node 22, yarn 4.12.0)
Actual (main 61cfb9b):
With only the is-number patch staged, the run is
partialFailure/ exit 1 with the samepackage_not_installedskip.Expected vs actual
.store/is-number-npm-6.0.0-<hash>/packageis found and patched, as the docs above promise for isolated-store transitive dependencies (and as already happens for npm's.store, pnpm's.pnpmand Yarn 3's pnpm linker).package_not_installed, file untouched, exit 0 when anything else applied.OS × version
.storenode_modules/is-numberpatched)macOS and Windows weren't probed. The layout is yarn's own (symlinks on POSIX, junctions on Windows), so they're likely affected the same way.
First bad version
Not a regression. Release 4.0.0 (npm
@socketsecurity/socket-patch) behaves identically on the same tree. 61cfb9b didn't extend.storesupport to Yarn's layout.Suspect code
crates/socket-patch-core/src/crawlers/npm_crawler.rs:1140: invisit_resolver_dir, a store-entrynode_modulesrejects any target that isn't a real directory (is_real_package_dir_sync,npm_crawler.rs:2361, usessymlink_metadata(..).is_dir()). Yarn'snode_modules/<name> -> ../packageis a symlink to the entry's ownpackage/dir, which is the only physical copy. Nothing walks<entry>/packagedirectly.list_npm_store_entries_sync/decode_npm_store_entry_name(npm_crawler.rs:231, ~1951) parse npm's<name>@<version>-<hash>key. Yarn's keys are<slug>-npm-<version>-<hash>(scoped:@scope-name-npm-…), so the advertised name/version is never decoded for them either.No probe runs yet. macOS/Windows are on the routine's backlog.