Repository navigation
Yarn berry reads the wrong nodeLinker when a ${VAR} in .yarnrc.yml is set from .env.yarn: vendored falsely refuses a node-modules project, and agent misses a live PnP project #1611
Description
[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).
Summary
Yarn 4 loads the project's .env.yarn file by default (injectEnvironmentFiles, default [".env.yarn?"]). It uses those variables when it expands ${…} in .yarnrc.yml. socket-patch expands ${…} in nodeLinker from this process's environment only (the #1100 fix). It never reads .env.yarn, so it resolves a different linker from the one yarn uses:
nodeLinker: "${L:-pnp}"with.env.yarnL=node-modules: yarn installsnode_modules/.scan --mode vendoredrefuses it as Plug'n'Play (vendor_yarn_berry_unsupported, exit 1).nodeLinker: "${L:-node-modules}"with.env.yarnL=pnp: yarn installs PnP (.pnp.cjs, nonode_modules).scan --mode agentexits 0, reporting the vulnerable package aspackage_not_installed. Theyarn_pnp_unsupportedwarning never fires, so the user sees nothing to patch.
These are the #1100 symptoms, from a variable source that the #1100 fix doesn't cover.
Impact
- Vendored mode can't be used on a node-modules project that picks its linker through
.env.yarn, even though the refusal's own remedy ("setnodeLinker: node-modules") already describes the project. - In agent mode, a live PnP project gets a clean exit 0 and a "not installed" skip instead of the documented PnP refusal, so a vulnerable dependency goes unpatched without any warning.
Repro (Linux, Node 22, yarn 4.0.2 and 4.18.1)
mkdir app && cd app
echo '{"name":"app","version":"1.0.0","private":true,"dependencies":{"left-pad":"1.3.0"}}' > package.json
printf 'nodeLinker: "${L:-pnp}"\nenableGlobalCache: false\n' > .yarnrc.yml
echo 'L=node-modules' > .env.yarn
touch yarn.lock && yarn install
yarn config get nodeLinker # node-modules; node_modules/ exists, no .pnp.cjs
socket-patch scan --mode vendored --json --yes # (patch API mock serving a left-pad@1.3.0 patch)
# -> partialFailure, exit 1: failed vendor_yarn_berry_unsupported
# "the configured yarn linker is `nodeLinker: pnp` ..."
# The other direction:
printf 'nodeLinker: "${L:-node-modules}"\nenableGlobalCache: false\n' > .yarnrc.yml
echo 'L=pnp' > .env.yarn
rm -rf node_modules && yarn install # .pnp.cjs, no node_modules
socket-patch scan --mode agent --json --yes
# -> success, exit 0, skipped package_not_installed, no yarn_pnp_unsupported warningControls: when L is exported in the shell instead (L=node-modules socket-patch scan --mode vendored … / L=pnp socket-patch scan --mode agent …), vendored applies (exit 0) and agent warns yarn_pnp_unsupported. That's the #1100 fix working. Hosted mode is unaffected in both directions.
Expected vs actual
- Expected:
docs/testing/yarn-berry-compatibility.mdsays Plug'n'Play "is decided by the configured linker (YARN_NODE_LINKER, else the nearest rc file … that setsnodeLinker…)". After Yarn berry PnP refusal misses a live Plug'n'Play project whose.yarnrc.ymlsetsnodeLinkerthrough${VAR}interpolation: agent apply exits 0 "not installed" and lock-only vendor passes the PnP gate (regression from #978) #1100, the value is read "as yarn resolves it" (crawlers/pkg_managers.rs,YarnEnv::resolve_linker). Yarn resolves it with.env.yarnapplied. - Actual:
.env.yarnis ignored, so the PnP gate fires on a node-modules project (vendored) and stays silent on a PnP project (agent).
Cells
| OS | yarn | rc / .env.yarn |
mode | result |
|---|---|---|---|---|
| Linux | 4.0.2 | ${L:-pnp} / L=node-modules |
vendored | fail (false vendor_yarn_berry_unsupported, exit 1), 2/2 |
| Linux | 4.18.1 | ${L:-pnp} / L=node-modules |
vendored | fail, 2/2 |
| Linux | 4.0.2 | ${L:-node-modules} / L=pnp |
agent | fail (package_not_installed, no PnP warning, exit 0), 2/2 |
| Linux | 4.18.1 | ${L:-node-modules} / L=pnp |
agent | fail, 2/2 |
| Linux | 4.18.1 | same, L exported in the process env |
vendored / agent | pass (control) |
| Linux | 4.18.1 | either | hosted | pass |
| macOS / Windows | — | — | — | not probed (probe branches blocked, see ledger #305) |
.env.yarn is a yarn 4 feature, so yarn 2/3 aren't affected. I tested main 657fce2 (= release 5.0.1). This has been the behaviour since the #1100 fix: before that, ${…} wasn't expanded at all.
Suspect code
crates/socket-patch-core/src/crawlers/pkg_managers.rs:208(YarnEnv::var):${NAME}reads onlystd::env::var.YarnEnv::current()(:182) builds the env without the project's.env.yarn(or theinjectEnvironmentFileslist it names).- Other readers of the same expansion that probably share the gap (not verified here):
formats/yarn/rc.rs:44process_setting(used bypatch/shared_store.rs:356fornodeLinker/pnpmStoreFolder), andpatch/redirect/upstream/npm.rs:605(npmRegistryServer/npmScopesfor the hosted upstream restore, where a.env.yarn-only${REG:-https://registry.npmjs.org}would expand silently to the fallback).
Note that yarn applies .env.yarn values only where the process environment doesn't already set the variable. A fix should keep that precedence and still honour injectEnvironmentFiles when it's overridden or emptied.
Activity
mikolalysenko commentedon Oct 16, 2026
Correction to the last note in the description: I tested the precedence, and on yarn 4.0.2 and 4.18.1
.env.yarnoverrides a variable that is already exported. WithL=pnpexported in the shell and.env.yarncontainingL=node-modules,yarn config get nodeLinkerprintsnode-modules. A fix should therefore let the.env.yarnvalue win over the process environment (and still honour an overridden or emptiedinjectEnvironmentFiles). As a result, even the exported-variable "control" in the description resolves differently from yarn whenever.env.yarnsets the same variable.Generated by Claude Code
mikolalysenko commentedon Oct 16, 2026
[agent] Triaged as
priority:p1(yarn berry, npm family). Confirmed on main657fce2: no code undercrates/reads.env.yarnorinjectEnvironmentFiles.YarnEnv::current()(crawlers/pkg_managers.rs) setsvars: None, soYarnEnv::varexpands${NAME}fromstd::env::varalone. This is a gap the #1100 fix left, not a duplicate of #1100. No open PR covers it. A fix should build the variable map once (process env overlaid by theinjectEnvironmentFilesfiles, with.env.yarnwinning, as the follow-up comment above found) and share it withformats/yarn/rc.rsprocess_settingand the hosted upstream registry reader.Generated by Claude Code
mikolalysenko commentedon Oct 23, 2026
[agent] Claiming this issue (shared root cause: yarn
${VAR}expansion in .yarnrc.yml reads only the process env, never the project's injectEnvironmentFiles / .env.yarn). Branch: agent/fix-yarn-env-file-expansion. Claim-ID: 2026-10-23T00:21:07Z-ee5f31Generated by Claude Code
mikolalysenko commentedon Oct 23, 2026
[agent] Draft PR: #1676
Generated by Claude Code
Read .env.yarn when expanding yarnrc variables
Merge injectEnvironmentFiles across rc files