Repository navigation
Agent mode misses transitive packages in Yarn's pnpm-linker store when pnpmStoreFolder moves it out of node_modules: skipped as package_not_installed, or refused as "first-party source" #859
Description
Activity
- addedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:yarn-berryYarn Berry (2+)Yarn Berry (2+)
on Oct 5, 2026 mikolalysenko commented
on Oct 5, 2026 CollaboratorAuthorMore actions[agent] Triage: priority:p1. Yarn Berry (npm-family). Not a duplicate. Related pattern but separate code path: the crawler only recognises Yarn's pnpm-linker store at node_modules/.store (#495), while pnpm's own relocated store is handled through .modules.yaml. No open PR covers it.
Generated by Claude Code
mikolalysenko commented
on Oct 5, 2026 CollaboratorAuthorMore actions[agent] Yarn Berry (2+) bug-hunt routine (ledger #305), re-check on main
9c43dfc: the transitive-package cells still fail exactly as filed (.cache/.store→package_not_installed, exit 0;store→ "first-party source", exit 1; yarn 4.18.1).A new variant in the same feature: when the store is relocated through Yarn's environment or home configuration instead of the project
.yarnrc.yml, even direct dependencies are refused.in_yarn_pnpm_store(crates/socket-patch-core/src/patch/shared_store.rs:235-279) readsnodeLinkerandpnpmStoreFolderonly from.yarnrc.ymlfiles in the project's ancestors. Yarn also takes both fromYARN_NODE_LINKER/YARN_PNPM_STORE_FOLDER(a common way to set them in CI) and from~/.yarnrc.yml. When either is set that way, the direct dep'snode_modules/left-pad -> ../.cache/.store/left-pad-npm-1.3.0-<hash>/packagelink no longer looks like a store entry, soapplyrefuses it as first-party source:Error: Failed to patch pkg:npm/left-pad@1.3.0: Refusing to patch …/.cache/.store/left-pad-npm-1.3.0-0382e69409/package: node_modules links to it, but it is outside every node_modules tree (a workspace member, a `file:` or `link:` directory dependency, or an `npm link` target), so it is first-party source that no reinstall restores …yarn config direct dep left-pad(agentscan→apply)4.18.1 .yarnrc.ymlwithoutnodeLinker;YARN_NODE_LINKER=pnpm YARN_PNPM_STORE_FOLDER=.cache/.storefor install and scanrefused, exit 1, unpatched (scan and applyboth)4.12.0 same refused, exit 1 4.12.0 nodeLinker: pnpmin the rc,YARN_PNPM_STORE_FOLDER=.cache/.storein envrefused, exit 1 4.12.0 / 4.18.1 YARN_NODE_LINKER=pnpmin env, default store (node_modules/.store)pass (direct + transitive patched) 4.12.0 control: nodeLinker: pnpm+pnpmStoreFolder: .cache/.storein the project rcpass 4.18.1 pnpmStoreFolder: .cache/.storein~/.yarnrc.yml(yarn resolves it against$HOME, so the store is shared outside the project)refused, exit 1, "first-party source". Refusing a store shared across projects is arguably right, but the message misdiagnoses it as a linked workspace or npm linktargetThese refusals are loud (exit 1), unlike the silent transitive skip in the original report. They're still a refusal firing on a registry install that the rc-configured twin patches. The
scan --mode agent --jsonenvelope also reports the refused package asaction: "added",failed: 0(#424). A fix that resolves the store location for #859 probably wants the same resolver here: env > project rc chain > home rc, as Yarn applies them.
Generated by Claude Code
- added a commit that references this issue
on Oct 5, 2026
[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).
Summary
With Yarn 4's pnpm linker, a transitive dependency exists only as a store entry (
<store>/<slug>/package). Its parent links to it from the parent entry's ownnode_modules. The crawler only recognizes that store at its default location,node_modules/.store(the #495 fix). When.yarnrc.ymlrelocates it withpnpmStoreFolder, agent mode fails on transitive packages in one of two ways, depending on the folder name:pnpmStoreFolder: .cache/.store, the example docs/ecosystems.md itself uses): the walk skips hidden directories, so the copy is never found.scan --mode agentreports the package asnotInstalled: true, skips the patch aspackage_not_installed, and exits 0success. The vulnerable copy that Node actually loads stays unpatched, and nothing warns.pnpmStoreFolder: store): the walk entersstore/<parent-slug>/node_modules/and finds the link. Butapplythen refuses it withapply_failed: "node_modules links to it, but it is outside every node_modules tree (a workspace member, afile:orlink:directory dependency, or annpm linktarget), so it is first-party source". That's exit 1, for a registry copy that docs/ecosystems.md says is patched.Direct dependencies (
node_modules/<dep>→<store>/<slug>/package) are patched correctly in both layouts; that's the #634 path. With the default store, the same transitive package is patched.Impact
pnpmStoreFolderis the setting Yarn documents for moving the store (for example out ofnode_modulesfor tooling or caching). Most patched vulnerabilities are in transitive dependencies, so on such a project agent mode silently leaves them unpatched (case 1) or fails the run (case 2).vexdoesn't attest the skipped copy, so there's no false attestation. Hosted and vendored modes work from the lockfile and aren't affected.Repro (Linux, yarn 4.18.1 from
@yarnpkg/cli-dist)Patch data came from a local mock of the patch API (
batch,by-package,viewwithblobContent/beforeBlobContent,patches/package), serving a patch that prepends a marker tois-number@6.0.0/index.js. A second package was also checked:esbuild@0.21.5→@esbuild/linux-x64@0.21.5, patchingREADME.md.(As a side note, in case 2 the
scan --jsonenvelope shows the failed patch asaction: "added"withfailed: 0and no error. That's #424, not this issue.)Expected vs actual
node_modulestrees are crawled") says the package stores of isolated layouts are walked "since they are the only home of transitive dependencies", including Yarn 4's pnpm-linkernode_modules/.storeand, for pnpm, "the directory avirtualStoreDirsetting moved it to". It also says links "into Yarn's pnpm-linker store relocated outsidenode_modules" (an active pnpm linker withpnpmStoreFolderin the nearest.yarnrc.yml) "are patched as usual". A transitive store entry under the relocated store should be patched exactly like one undernode_modules/.store.package_not_installed) or refused as first-party source (exit 1).Matrix (Linux, Node 22)
pnpmStoreFolderis-number,@esbuild/linux-x64)left-pad).cache/.store4646693package_not_installed, exit 0 (2 packages, 2 runs).cache/.store4646693package_not_installed, exit 0store4646693apply_failed"first-party source", exit 1 (2 runs)store4646693apply_failed"first-party source", exit 1node_modules/.store)4646693.cache/.storepackage_not_installed(same)pnpmStoreFolderas an unknown setting)First bad: this isn't a regression. Release 4.0.0 skips the same way. macOS and Windows weren't probed (the routine can't push probe branches right now), but the crawl logic doesn't depend on the OS.
Suspect code
crates/socket-patch-core/src/crawlers/npm_crawler.rs:1674: the Yarn/npm linked store is only recognized asnode_modules/.store. Nothing readspnpmStoreFolderthe way the pnpm path readsvirtualStoreDirfrom.modules.yaml(npm_crawler.rs:143-160).:1720then skips hidden directories, which explains case 1.crates/socket-patch-core/src/patch/shared_store.rs:241/:274(in_yarn_pnpm_store):projectis taken asnode_modules.parent(). For a link inside a store entry's ownnode_modules, that "project" is the store entry itself, soproject.starts_with(&store)holds, the function returns false, and the copy is classifiedLinkedSource. That explains case 2. The project root could be found from theyarn.lock/.yarnrc.ymlancestor instead..yarnrc.ymlsetsnodeLinker: pnpmandpnpmStoreFolder, walk<store>/*/packageas store entries, the same waylist_npm_store_entries_synchandlesnode_modules/.store.Backlog review — 2026-10-08
Priority: P1 → P2. Relocated Yarn store layouts lose transitive discovery; retain at P2 with the environment trigger explicit.