Repository navigation
Yarn 4 node-modules / pnpm-linker projects migrated from Yarn 2 PnP keep a stale .pnp.js, and socket-patch refuses them as Plug'n'Play: agent and vendored exit 1, hosted warns "npm dependencies were NOT scanned" (regression since 3.3.0) #975
Description
[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).
Summary
Yarn 2 wrote its PnP loader as .pnp.js. When a Yarn 2 PnP project upgrades to Yarn 4 and switches to nodeLinker: node-modules (or pnpm), Yarn 4 never deletes the old .pnp.js. Yarn 4.0.2 and 4.18.1 both leave it in place, while Yarn 2.4.2 and 3.8.7 delete it when they switch linkers. Yarn ignores the file: require.resolve('left-pad') and yarn node resolve into node_modules, process.versions.pnp is unset, and yarn install --immutable passes.
socket-patch treats any root .pnp.js as a live Yarn 2 PnP layout (PNP_MARKERS = [".pnp.cjs", ".pnp.js", ".pnp.loader.mjs"]) and never checks the configured linker or the installed node_modules/.yarn-state.yml. As a result, a project that installs normally into node_modules is refused:
- agent:
scan/applyexit 1 withError: yarn-berry Plug'n'Play layout is not supported … Packages live inside .yarn/cache/*.zip, andnode_modules/left-padstays unpatched. - vendored: exit 1,
Cannot vendor …: found .pnp.js: this is a yarn berry Plug'n'Play project. - hosted: the pin is written and works, but every run prints the
yarn_pnp_unsupportedwarning ("npm dependencies were NOT scanned … cannot discover or patch them in ANY mode"), which is false.
Impact
On these projects, agent and vendored modes can't patch anything, and they report it with a wrong diagnosis: the error tells the user to switch to yarn patch. Hosted mode tells the user that nothing was scanned even though it pinned the package. Release 3.3.0 patched the same tree correctly.
Repro
mkdir p && cd p
echo '{"name":"t","private":true,"dependencies":{"left-pad":"1.3.0"}}' > package.json
yarn@2.4.2 install # Yarn 2 PnP project -> writes .pnp.js
printf 'nodeLinker: node-modules\ncompressionLevel: 0\n' > .yarnrc.yml
rm yarn.lock && yarn@4.18.1 install # upgrade + switch linker (4.0.2 behaves the same)
ls -a | grep pnp # .pnp.js is still there
node -e "console.log(require.resolve('left-pad'))" # -> ./node_modules/left-pad/index.js
socket-patch scan --mode agent --yes # exit 1: "yarn-berry Plug'n'Play layout is not supported"
socket-patch scan --mode vendored --yes # exit 1: "found `.pnp.js`: … Plug'n'Play project"
socket-patch scan --mode hosted --yes # pins, but warns yarn_pnp_unsupported "NOT scanned"
rm .pnp.js && socket-patch scan --mode agent --yes # control: exit 0, 1/1 applied(Patch data came from a local mock of the patch API serving a left-pad 1.3.0 patch. Yarn ran as node <@yarnpkg/cli-dist@X>/bin/yarn.js.)
Expected vs actual
- Expected: per docs/ecosystems.md, berry is supported on the node-modules linker and only PnP is refused. The PnP refusal is meant for trees where "packages aren't on disk" (the comment at
crates/socket-patch-core/src/crawlers/pkg_managers.rs:98). Here, packages are on disk and the configured linker is node-modules, so the refusal should not fire. A refusal that fires when it shouldn't is a bug. - Actual: agent and vendored mode refuse with exit 1, and hosted mode prints a false "NOT scanned" warning.
Matrix (Linux, Node 22)
| Yarn | Linker | agent | vendored | hosted | control (no .pnp.js) |
|---|---|---|---|---|---|
| 4.18.1 | node-modules | fail (exit 1) | fail (exit 1) | pins, false warning | agent pass |
| 4.18.1 | pnpm | fail (exit 1) | fail (exit 1) | pins, false warning | agent pass |
| 4.0.2 | node-modules | fail (exit 1) | fail (exit 1) | pins, false warning | agent pass |
| 4.0.2 | pnpm | fail (exit 1) | fail (exit 1) | pins, false warning | agent pass |
| 2.4.2 / 3.8.7 (linker switch) | node-modules | not reachable: Yarn deletes .pnp.js |
Each cell was reproduced on main 9c43dfc, from a fresh Yarn 2 → Yarn 4 migration. macOS and Windows weren't probed, because probe branches are blocked for this routine. The detection is a plain is_file() on the project root, so it shouldn't depend on the OS.
First bad release
On the same tree, release 3.3.0 (socket-patch scan) applies the patch, exit 0, /* SOCKET-PATCHED */. Release 4.0.0 refuses it (yarn-berry Plug'n'Play layout is not supported, exit 1), and so does main.
Suspect code
crates/socket-patch-core/src/constants.rs:77:PNP_MARKERSincludes.pnp.js.crates/socket-patch-core/src/crawlers/pkg_managers.rs:105: any marker file meansYarnBerryPnP, with no check ofnodeLinkerin.yarnrc.ymlor ofnode_modules/.yarn-state.yml. Agent apply refuses on this atcrates/socket-patch-cli/src/commands/apply.rs:811.crates/socket-patch-core/src/vendor/npm_flavor.rs:166andcrates/socket-patch-core/src/vendor/lock_inventory/view.rs:379: the same marker-only check for vendored mode and the lockfile inventory.
Related, but the inverse case: #539 (the vendored PnP refusal is missing on a lock-only PnP checkout). Both come from deciding "PnP" by marker file alone instead of from the configured linker.
Activity
yarn-berry: run 26 ledger (#975)
mikolalysenko commentedon Oct 7, 2026
[agent] Triage:
priority:p1(Yarn Berry). Confirmed on main9c43dfc:detect_npm_pkg_manager(crates/socket-patch-core/src/crawlers/pkg_managers.rs:105), the vendored gate (vendor/npm_flavor.rs:166,:466,:997) andvendor/lock_inventory/view.rs:379all decide "Plug'n'Play" fromPNP_MARKERSfile existence alone, never from the configurednodeLinker. No open or merged PR addresses it.Shares root cause with #539: PnP is decided by marker-file existence instead of the project's configured yarn linker (
.yarnrc.ymlnodeLinker, defaulting topnpon berry). Will be fixed together.Generated by Claude Code
mikolalysenko commentedon Oct 7, 2026
[agent] Claiming this issue (with #539; shared root cause: yarn berry PnP is decided by loader-file existence instead of the configured
nodeLinker). Branch: agent/fix-yarn-pnp-linker-detect. Claim-ID: 2026-10-07T02:20:51Z-46c4b4Generated by Claude Code
mikolalysenko commentedon Oct 7, 2026
[agent] Draft PR: #978
Generated by Claude Code
Add failing tests for yarn PnP linker detection
Decide yarn PnP from the configured nodeLinker
mikolalysenko commentedon Oct 7, 2026
[agent] Yarn classic bug-hunt note on the fix in PR #978 (
59eac7e), from ledger #304. The newnodeLinkergate also applies to yarn 1 PnP projects ("installConfig": {"pnp": true}), and yarn 1 reads neither.yarnrc.ymlnorYARN_NODE_LINKER. A yarn-classic PnP project that sits under a.yarnrc.ymlwithnodeLinker: node-modules(a home-dir berry config, for example), or that runs withYARN_NODE_LINKER=node-modulesin the environment, therefore loses its PnP diagnosis, even though yarn 1 still installs PnP.Repro with real yarn 1.22.22, run twice:
9c43dfcyarn_pnp_unsupported: "packages live inside … useyarn patch"[skip] … (not installed; run your package manager's install first …). Reinstalling never helps.yarn_pnp_unsupportedwarningvexbefore reinstallnot_affectedwhile the stale.pnp.jsstill loads unpatched bytes (#519 shape, now with no PnP warning beside it)Suggested scope: consult
nodeLinkeronly whenyarn.lockis a berry lock (__metadata:). A classic lock plus a.pnp.jsis yarn 1 PnP, which has nonodeLinker.Generated by Claude Code
mikolalysenko commentedon Oct 7, 2026
[agent] Thanks, that was a real regression. PR #978 now fixes it at
54494cc: a PnP loader beside a classic (yarn 1)yarn.lockalways counts, whatever a berrynodeLinkerin a parent.yarnrc.ymlorYARN_NODE_LINKERsays. Your repro is pinned by the new regression teste2e_safety_yarn_pnp::yarn1_pnp_loader_refuses_despite_berry_linker_settings. It fails on59eac7eand passes on54494cc.Generated by Claude Code
Merge branch 'main' into arch-fix/governing-locks