Skip to content

Fix yarn .env.yarn ignored in rc expansion (#1611) - #1676

Merged
Mikola Lysenko (mikolalysenko) merged 7 commits into
mainfrom
agent/fix-yarn-env-file-expansion
Oct 23, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 7 commits into
mainfrom
agent/fix-yarn-env-file-expansion

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #1611

Summary

Yarn 4 loads the dotenv files named by injectEnvironmentFiles (.env.yarn
by default) before it expands ${NAME} in .yarnrc.yml. A value from those
files wins over an exported variable. socket-patch expanded the references
from its own process environment only, so it resolved a different
nodeLinker from the one yarn uses:

  • nodeLinker: "${L:-pnp}" + .env.yarn L=node-modules: vendored mode
    falsely refused a node-modules project with vendor_yarn_berry_unsupported
    (exit 1).
  • nodeLinker: "${L:-node-modules}" + .env.yarn L=pnp: agent mode missed
    a live PnP project, skipped the package as package_not_installed and
    exited 0 without the yarn_pnp_unsupported refusal, so the package stayed
    unpatched with no warning.

Root cause

YarnEnv::var (the linker probe), formats::yarn::rc::process_setting (the
yarn pnpm-store gate in patch/shared_store.rs) and the hosted upstream
registry reader (patch/redirect/upstream/npm.rs) each expanded ${NAME}
from std::env alone.

Fix

  • formats::yarn::rc: raw_list_setting reads injectEnvironmentFiles (a
    sequence, a flow list or a comma string; [] empties it) through the same
    YAML walk as every scalar setting. env_file_paths builds the list the way
    yarn does: YARN_INJECT_ENVIRONMENT_FILES when set; else every rc file's
    list concatenated (home first, then ancestors from the farthest to the
    nearest, each entry against its own rc folder, so a nearer [] clears
    nothing; the walk stops at {onConflict: reset, value: [...]}, before
    hardReset, and a string list replaces the others); else .env.yarn?
    in the folder yarn runs in. As in yarn, a string list's items resolve
    against the folder yarn runs in and keep their spaces, and ${…} in an
    entry expands with no variable set (yarn reads the list before its
    environment). parse_dotenv mirrors the
    dotenv parser yarn uses (export, quotes, multi-line values, comments).
    injected_env expands each value's ${…} against the values injected so
    far, then the environment, as yarn does.
  • YarnEnv gains lazily loaded env_files that win over the environment in
    var(). YarnEnv::for_project is used by yarn_node_linker, the yarn
    pnpm-store gate and the upstream registry reader. process_setting is
    removed, so no reader expands from the bare process environment.
  • The env files are read only when a ${NAME} is actually expanded.

I checked every rule against a real yarn 4.9.1 (yarn config get nodeLinker): a file value overrides an exported one; a later file wins; a
missing ? file is skipped; [] adds no files; rc lists concatenate home →
farthest → nearest; an rc entry resolves against its rc folder; the default .env.yarn resolves against the folder
yarn runs in, not the lockfile root; and ${…} in a dotenv value expands,
including references to earlier injected values. A missing required file makes
yarn crash, so the ? marker carries no meaning here and is dropped.

Not changed (reviewed)

  • The in-memory repository view (ProjectView::Memory, napi hosted engine)
    still resolves the linker from the repository's rc files with no
    variables (YarnEnv::repository_only). Its file selection doesn't fetch
    .env.yarn (normally untracked) either, so supporting it means widening
    hosted/memory/select.rs. That is out of scope for this CLI fix.
  • No gate on the yarn version. .env.yarn is a yarn 4 feature, but the lock
    version doesn't identify the binary: yarn 4.9.1 applies .env.yarn to a
    project with a __metadata.version: 6 lock. A yarn 2/3 project whose
    .env.yarn sets the same variable its nodeLinker reads is far-fetched.
  • The npm/PyPI/gem wrappers only dispatch the binary and have no yarnrc
    logic, so they need no change.

Tests (red → green)

Issue shape Test Before After
#1611 vendored, ${L:-pnp} + .env.yarn node-modules (also custom injectEnvironmentFiles, emptied list still refuses) in_process_vendor::berry_env_file_linker_decides_vendoring fail (exit 1 vendor_yarn_berry_unsupported) pass
#1611 agent, live PnP via .env.yarn (also with a conflicting exported value) e2e_safety_yarn_pnp::pnp_loader_under_env_file_pnp_linker_refuses fail (exit 0) pass
#1611 agent, stale loader + .env.yarn node-modules (export, quotes, comment) e2e_safety_yarn_pnp::stale_pnp_loader_under_env_file_linker_applies fail (refused) pass
precedence and folders pkg_managers::tests::env_files_feed_rc_references, env_files_resolve_against_their_rc_folder — pass
parsing rc::tests::dotenv_reads_like_yarn, list_settings_read_like_yarn, list_settings_need_a_file_yarn_reads, env_file_paths_follow_precedence, injected_values_expand_like_yarn — pass

Local: cargo fmt --all -- --check and cargo clippy --workspace --all-features -- -D warnings are clean. cargo test --workspace --all-features --no-fail-fast passes except for 10 tests that fail identically on main in this sandbox and have nothing to do with yarn: 9 permission-denied tests (the sandbox runs as root, so chmod can't make writes fail: repair x2, in_process_redirect x3, core copy_tree / vlt_heal / pypi_poetry / pypi_requirements) and e2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managed (needs patches-api.socket.dev, which the sandbox can't reach). The real-yarn berry e2e suite couldn't run locally because corepack can't reach repo.yarnpkg.com, so CI covers it.
/code-review high ran on the diff. Its findings are fixed in the third commit, except the two listed under "Not changed".

https://claude.ai/code/session_01JsUVcdSrpjkK1bKHJrZzq4


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
Yarn 4 loads the files named by injectEnvironmentFiles (.env.yarn by
default) before it expands ${NAME} in .yarnrc.yml, and a value from
those files wins over an exported variable. socket-patch expanded the
references from its own environment only, so a project that picks its
nodeLinker through .env.yarn was misread: vendored mode refused a
node-modules project as Plug'n'Play, and agent mode missed a live PnP
project, skipping the package as not installed with exit 0.

The linker probe, the yarn pnpm-store gate and the hosted upstream
registry reader now expand with the injected files applied. The file
list follows yarn: YARN_INJECT_ENVIRONMENT_FILES, else the nearest rc
file that sets it (relative to that file), else .env.yarn in the
project; an emptied list loads nothing and a later file wins.

Fixes #1611

Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
Review follow-ups for the .env.yarn fix:

- Expand ${NAME} inside .env.yarn values as yarn does, against the
  values injected so far and then the environment, so a linker set
  through a chained variable resolves the way yarn resolves it.
- Read injectEnvironmentFiles through the same YAML walk as every
  other setting, so a file yarn refuses (a list root, two documents,
  a doubled BOM) sets no list here either.
- Load the env files only when a ${NAME} is actually expanded, and
  reuse the same lookup in the hosted registry reader.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 23, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Ready for review at d645e11. CI: every check on this head passed or was skipped by path filters (ci-ok, clippy, coverage, yarn-berry 4.18.0, yarn-classic, hosted-e2e and all e2e legs green); no main-wide failures. The branch merges cleanly with current main (aa69125). Bugbot reviewed d645e11 with no findings, and there are no review threads. Reviewers should look at formats::yarn::rc (env_file_paths precedence and parse_dotenv) and the YarnEnv::var override order. Slack announcement not sent: no Slack send tool was available in this run.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief (head d645e11)

What it does. Yarn 4 loads the dotenv files named by injectEnvironmentFiles (.env.yarn by default) before it expands ${NAME} in .yarnrc.yml, and a value from those files beats an exported one. socket-patch used to expand from its own process environment only, so it could pick a different nodeLinker from the one yarn uses. Vendored mode could wrongly refuse a node-modules project, and agent mode could skip a live PnP project and exit 0 (#1611). Now the linker probe, the yarn pnpm-store gate and the hosted registry reader all expand through the same injected-env lookup.

Risk: low. The env files are read lazily, and only when a ${NAME} is actually expanded, so projects without references behave exactly as before. The new code only reads files (the dotenv files yarn itself reads, inside the project tree or the home folder) and writes nothing.

Look here

Verified

  • I read the full diff (8 files, +589/−21). CHANGELOG.md is untouched and I found no debug leftovers or unrelated edits.
  • Locally: 104 core tests pass (formats::yarn::rc, crawlers::pkg_managers, shared_store, upstream::npm), and so do both new e2e_safety_yarn_pnp env-file tests.
  • Red check: I put back YarnEnv::current() in yarn_node_linker. Both new e2e tests then fail, so the tests exercise the fix.
  • cargo clippy -p socket-patch-core --all-targets reports nothing in the files this PR touches. The lints it does report are in other files and exist on main too.
  • CI: 36/36 checks that ran are green (50 skipped by path filters), including ci-ok, clippy and the yarn-berry/e2e legs. Bugbot found no issues on d645e11, and no review threads are open.

Changes I made: none.

Open questions (not blocking). The pnpm-store gate looks for the default .env.yarn in node_modules' parent folder. In a monorepo whose node_modules sits under a workspace package, that folder isn't where yarn runs, so the gate could miss the file. That layout is rare with the pnpm linker. The in-memory hosted view also still ignores .env.yarn. The PR description already lists that as out of scope.

Auto-merge (squash) is armed, so approving sends this straight to the merge queue.


Generated by Claude Code

Comment thread crates/socket-patch-core/src/formats/yarn/rc.rs Outdated
Yarn 4 concatenates the injectEnvironmentFiles list of every rc file
it reads instead of taking the nearest one: the home rc file's
entries load first, then each ancestor's from the farthest to the
nearest, each against its own folder. A nearer [] adds nothing and
clears nothing. Taking only the nearest list could drop a parent's
env file, misreading nodeLinker the same way #1611 did. Checked
against yarn 4.9.1.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/formats/yarn/rc.rs
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final reviewer: disabled auto-merge. My review brief covered d645e11, and 1a3bb28 (rc-list merge review fix) landed after it. There is also one unresolved review thread (crates/socket-patch-core/src/formats/yarn/rc.rs:232). I'll re-review once CI and Bugbot settle and the thread is resolved, then re-arm auto-merge if nothing blocks.


Generated by Claude Code

Yarn 4 stops merging injectEnvironmentFiles across rc files at a list
written as {onConflict: reset, value: [...]} (kept) or hardReset
(dropped too), and a list given as a string replaces the others
outright. Without that, a nearer reset still let a parent folder's
env file pick nodeLinker. The YAML walk now reads the onConflict
wrapper. Checked against yarn 4.9.1.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief (head a4e4bb8)

This updates my brief on d645e11. Everything said there still holds. Two commits have landed since then, both review fixes: 1a3bb28 and a4e4bb8.

What it does. socket-patch now expands ${NAME} in .yarnrc.yml the way Yarn 4 does: values from the injectEnvironmentFiles dotenv files (default .env.yarn) win over the process environment. That keeps the nodeLinker probe, the pnpm-store gate and the hosted registry reader in line with yarn (#1611). Since the last brief, the file list follows yarn's resolveValueAt merge instead of "nearest rc file wins":

  • every rc file's list is concatenated, lowest precedence first, so a nearer [] clears nothing
  • {onConflict: reset, value: [...]} keeps only its own list, and hardReset drops its own list too
  • a string value, or a change between string and list, stops the walk

Risk: low. This is read-only, and the new rules only matter when ${NAME} references are expanded. The top_level_values parser now reads {onConflict, value} mappings. Any other mapping value still parses as Other, as before.

Look here

Verified

  • I read the delta d645e11..a4e4bb8 and checked the merge order against yarn's resolveValueAt. CHANGELOG.md is untouched, and there are no debug leftovers.
  • Locally, merged with current main (ee40b70, clean merge): cargo test -p socket-patch-core --lib -- formats::yarn crawlers::pkg_managers passes 100/100.
  • Red check: going back to "the first rc file that sets the list wins" fails 2 tests (env_file_paths_follow_precedence and env_files_resolve_against_their_rc_folder).
  • CI on a4e4bb8: 35 passed, 19 skipped, 0 failed, including ci-ok and clippy. Cursor Bugbot passed, and both review threads are resolved.

Changes I made. None.

Open questions (non-blocking). The ones in the earlier brief still stand: the pnpm-store gate's .env.yarn lookup in a nested-workspace layout, and the in-memory hosted view. The wrapper reader tolerates malformed nesting such as value: [[a]] without failing. Yarn rejects that file anyway.

Auto-merge is armed, so approving sends it straight to the merge queue.


Generated by Claude Code

Comment thread crates/socket-patch-core/src/formats/yarn/rc.rs Outdated
Three cases where socket-patch picked a different .env file than
yarn 4, so the nodeLinker it worked out could differ:

- A list given as a string now resolves against the folder yarn runs
  in, not the rc file's folder, since yarn's string items carry no
  source file.
- ${NAME} in an entry now expands with no variable set: yarn reads
  this list before it loads any environment, so only the fallback
  applies.
- Comma-split items keep their spaces, as yarn's split does.

Checked against yarn 4.9.1.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 35dbcf1. Configure here.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

This updates my brief on a4e4bb8. Everything said there still holds. One commit has landed since then, a review fix: 35dbcf1.

What it does. socket-patch now expands ${NAME} in .yarnrc.yml the way Yarn 4 does: values from the injectEnvironmentFiles dotenv files (default .env.yarn) win over the process environment (#1611). The new commit changes how the file list itself is read, to match yarn:

  • a string value (".env.local?") resolves against the folder yarn runs in, not the rc file's folder
  • ${…} inside the list expands with no variable set, because yarn reads the list before it loads any environment
  • a comma-split item keeps its spaces (a, b names a and b)

Risk: low. This is read-only, and it only matters when .yarnrc.yml uses ${NAME} and sets injectEnvironmentFiles unusually. env_file_paths lost its var parameter. Its only caller is YarnEnv.

Look here

Verified

  • I checked all three claims against real yarn 4.9.1 (@yarnpkg/cli-dist, yarn config get from a workspace folder):
    • a root injectEnvironmentFiles: ".env.local?, .env.b?" read app/.env.local and app/ .env.b. It ignored the root .env.local.
    • ["${HOME:-.}/x?"] read <rc dir>/x even with HOME exported.
  • cargo test -p socket-patch-core --lib: yarn 375/375 and pkg_managers 48/48 pass. cargo fmt --check is clean.
  • Red check: resolving a string against the rc folder again fails env_file_paths_follow_precedence.
  • CHANGELOG.md is untouched, and there are no debug leftovers.
  • CI on 35dbcf1: 35 passed, 19 skipped, 0 failed, including ci-ok and clippy. Cursor Bugbot is clean, and all 3 review threads are resolved.

Changes I made. None.

Open questions. None new. The non-blocking ones from the earlier briefs still stand.

Auto-merge is armed, so approving sends it straight to the merge queue.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: re-confirmed Ready for review on the new head (label was first applied at an earlier head).

  • Head: 35dbcf13d58e72cc786a2af9c6ef3028429a37b6
  • CI: all check runs on this head green (no main-wide failures)
  • Mergeable, no conflicts; does not touch CHANGELOG.md
  • Bugbot reviewed this exact head: no new issues; all 3 review threads resolved
  • Auto-merge is armed (left untouched; nothing was pushed).

Generated by Claude Code

Merged via the queue into main with commit 518a84f Oct 23, 2026
54 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-yarn-env-file-expansion branch October 23, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

3 participants