[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).
Summary
Since #978 (fix for #975/#539), socket-patch decides yarn Plug'n'Play from the configured nodeLinker instead of from the .pnp.* loader. It reads that value as a literal string (yarnrc_scalar). Yarn itself expands ${NAME} / ${NAME:-default} in rc values. So a project whose .yarnrc.yml says nodeLinker: "${NODE_LINKER:-pnp}" (or "${L}" with L=pnp in the environment) is a real, installed PnP project by yarn's own account, and yarn config get nodeLinker prints pnp. socket-patch reads the literal ${NODE_LINKER:-pnp}, which isn't pnp, so it treats the live .pnp.cjs as stale:
Release 4.0.0 refuses the same installed tree correctly (it keyed on the loader), so the agent half is a regression from #978 (1f3f1f5).
Impact
A refusal that should fire doesn't, and the replacement message is wrong. The user is told the dependency isn't installed, with exit 0, instead of being told PnP isn't supported and pointed at yarn patch. CI that gates on the exit code sees success, and nothing is patched. VEX isn't affected: it still omits the package (package_not_found).
Repro (Linux, yarn 4.18.1 or 4.0.2 via @yarnpkg/cli-dist)
mkdir proj && cd proj
echo '{"name":"app","version":"0.0.0","private":true,"dependencies":{"left-pad":"1.3.0"}}' > package.json
printf 'nodeLinker: "${L:-pnp}"\n' > .yarnrc.yml
touch yarn.lock && yarn install # writes .pnp.cjs, no node_modules
yarn config get nodeLinker # -> pnp
# stage .socket/manifest.json + after blob for pkg:npm/left-pad@1.3.0 (package/index.js), offline
socket-patch apply --json # main: status success, skipped package_not_installed, exit 0
# lock-only: copy package.json, yarn.lock, .yarnrc.yml and .socket to a fresh dir
socket-patch vendor --offline --json # main: vendor_service_offline_conflict (the PnP gate passed)
Control: the same steps with nodeLinker: "pnp" give apply → yarn_pnp_unsupported, exit 1, and vendor → vendor_yarn_berry_unsupported.
Expected vs actual
docs/testing/yarn-berry-compatibility.md: "Plug'n'Play is decided by the configured linker … vendor refuses a lock-only PnP checkout up front", and apply refuses PnP with yarn_pnp_unsupported. The configured linker here is pnp (yarn's own config get), so both should refuse.
| Cell |
yarn says |
main e61a845 |
release 4.0.0 |
nodeLinker: "${L:-pnp}", installed PnP, agent apply (4.18.1, twice; 4.0.2) |
pnp |
exit 0, package_not_installed |
yarn_pnp_unsupported, exit 1 |
nodeLinker: "${L}" with L=pnp, agent apply (4.18.1) |
pnp |
exit 0, package_not_installed |
not run |
Same projects, lock-only vendor --offline |
pnp |
PnP gate passes (vendor_service_offline_conflict) |
n/a (#539 era) |
nodeLinker: "pnp" control |
pnp |
refused correctly |
refused |
The same literal parser also disagrees with yarn in the fail-closed direction. These are rarer and loud, and are listed only so a fix can cover them. Stub loader + node_modules fixture, yarn 4.18.1:
.yarnrc.yml / env |
yarn config get nodeLinker |
socket-patch apply |
"nodeLinker": node-modules (quoted key) |
node-modules |
refused as PnP |
nodeLinker: with node-modules on the next line |
node-modules |
refused as PnP |
{nodeLinker: node-modules} (flow map) |
node-modules |
refused as PnP |
env yarn_node_linker=node-modules (yarn lower-cases env keys; Linux/macOS) |
node-modules |
refused as PnP |
nodeLinker: "${L:-node-modules}" with L=pnp |
pnp |
applies (fail-open, same as above) |
OS: Linux only. Windows env keys are case-insensitive, so the last env row doesn't apply there.
Suspect code
crates/socket-patch-core/src/crawlers/pkg_managers.rs:203 rc_node_linker, and :252 yarn_linker_is_pnp (any value other than literal pnp counts as non-PnP).
crates/socket-patch-core/src/formats/yarn/berry_gates.rs:195 yarnrc_scalar (no ${…} expansion, and only a column-0 unquoted key: value line).
crates/socket-patch-core/src/crawlers/pkg_managers.rs:184: only the upper-case YARN_NODE_LINKER is read.
A fail-closed option: treat a value containing ${ as unknown, so it counts as PnP while a loader exists and is refused for lock-only vendoring. Or expand it from the environment the way yarn does.
No probe runs: Linux only. First bad commit: 1f3f1f5 (#978); release 4.0.0 is good.
[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).
Summary
Since #978 (fix for #975/#539), socket-patch decides yarn Plug'n'Play from the configured
nodeLinkerinstead of from the.pnp.*loader. It reads that value as a literal string (yarnrc_scalar). Yarn itself expands${NAME}/${NAME:-default}in rc values. So a project whose.yarnrc.ymlsaysnodeLinker: "${NODE_LINKER:-pnp}"(or"${L}"withL=pnpin the environment) is a real, installed PnP project by yarn's own account, andyarn config get nodeLinkerprintspnp. socket-patch reads the literal${NODE_LINKER:-pnp}, which isn'tpnp, so it treats the live.pnp.cjsas stale:applyno longer refuses withyarn_pnp_unsupported. It exits 0 withstatus: successand skips the package aspackage_not_installed("Resolved by the project lockfile but not installed on this host (lockfile-only)"). The package is installed, inside.yarn/cache, andnode -r ./.pnp.cjsresolves it.vendorpasses the Vendored yarn berry PnP refusal keys only on .pnp.cjs: a lock-only PnP checkout vendors successfully, then every re-run in an installed checkout fails exit 1 with vendor_yarn_berry_unsupported #539 up-front PnP gate. It goes on to the vendoring service (with--offlineit stops atvendor_service_offline_conflict), where the literalnodeLinker: "pnp"control refuses withvendor_yarn_berry_unsupported.Release 4.0.0 refuses the same installed tree correctly (it keyed on the loader), so the agent half is a regression from #978 (
1f3f1f5).Impact
A refusal that should fire doesn't, and the replacement message is wrong. The user is told the dependency isn't installed, with exit 0, instead of being told PnP isn't supported and pointed at
yarn patch. CI that gates on the exit code sees success, and nothing is patched. VEX isn't affected: it still omits the package (package_not_found).Repro (Linux, yarn 4.18.1 or 4.0.2 via
@yarnpkg/cli-dist)Control: the same steps with
nodeLinker: "pnp"giveapply→yarn_pnp_unsupported, exit 1, andvendor→vendor_yarn_berry_unsupported.Expected vs actual
docs/testing/yarn-berry-compatibility.md: "Plug'n'Play is decided by the configured linker …
vendorrefuses a lock-only PnP checkout up front", andapplyrefuses PnP withyarn_pnp_unsupported. The configured linker here ispnp(yarn's ownconfig get), so both should refuse.e61a845nodeLinker: "${L:-pnp}", installed PnP, agentapply(4.18.1, twice; 4.0.2)package_not_installedyarn_pnp_unsupported, exit 1nodeLinker: "${L}"withL=pnp, agentapply(4.18.1)package_not_installedvendor --offlinevendor_service_offline_conflict)nodeLinker: "pnp"controlThe same literal parser also disagrees with yarn in the fail-closed direction. These are rarer and loud, and are listed only so a fix can cover them. Stub loader +
node_modulesfixture, yarn 4.18.1:.yarnrc.yml/ envyarn config get nodeLinkerapply"nodeLinker": node-modules(quoted key)nodeLinker:withnode-moduleson the next line{nodeLinker: node-modules}(flow map)yarn_node_linker=node-modules(yarn lower-cases env keys; Linux/macOS)nodeLinker: "${L:-node-modules}"withL=pnpOS: Linux only. Windows env keys are case-insensitive, so the last env row doesn't apply there.
Suspect code
crates/socket-patch-core/src/crawlers/pkg_managers.rs:203rc_node_linker, and:252yarn_linker_is_pnp(any value other than literalpnpcounts as non-PnP).crates/socket-patch-core/src/formats/yarn/berry_gates.rs:195yarnrc_scalar(no${…}expansion, and only a column-0 unquotedkey: valueline).crates/socket-patch-core/src/crawlers/pkg_managers.rs:184: only the upper-caseYARN_NODE_LINKERis read.A fail-closed option: treat a value containing
${as unknown, so it counts as PnP while a loader exists and is refused for lock-only vendoring. Or expand it from the environment the way yarn does.No probe runs: Linux only. First bad commit:
1f3f1f5(#978); release 4.0.0 is good.