Repository navigation
Fix yarn PnP detection ignoring nodeLinker (#975, #539) - #978
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A Yarn 2 to Yarn 4 migration that switched nodeLinker away from pnp keeps a stale .pnp.js, and apply and vendor refuse the project as Plug'n'Play (#975). A lock-only checkout of a real PnP project has no loader yet, so vendor wires it and every later re-run refuses (#539). Assisted-by: Claude Code:claude-opus-5-5
Every Plug'n'Play check looked only for a .pnp.* loader file. A Yarn 2 to Yarn 4 migration that switched nodeLinker to node-modules or pnpm keeps the old .pnp.js, which yarn ignores, so apply and vendor refused a project whose packages are in node_modules and hosted scans warned that nothing was scanned (#975). Read yarn's effective nodeLinker (YARN_NODE_LINKER, else the nearest .yarnrc.yml that sets it) and ignore a loader the linker disowns. Forward vendoring also refuses a berry project configured for PnP (nodeLinker: pnp, or unset, berry's default) before its loader exists, so a lock-only checkout gets the same answer as an installed one instead of being wired once and refused on every re-run (#539). Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
|
[agent] Generated by Claude Code |
|
BugBot review Generated by Claude Code |
A pnpm node-linker=pnp tree writes the same .pnp.cjs as yarn. When an ancestor .yarnrc.yml or YARN_NODE_LINKER set a non-pnp yarn linker, the loader counted as stale and vendor wired the pnpm project instead of refusing it. Decide the pnpm case on any loader first, then apply the yarn linker check. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent] Ready for review at
Generated by Claude Code |
Two gaps in the nodeLinker-based PnP decision, both reported against this PR by the yarn bug-hunt routines: - Yarn 1 PnP (installConfig.pnp, a classic yarn.lock) has no nodeLinker, and yarn 1 reads neither .yarnrc.yml nor YARN_NODE_LINKER. A berry setting from a parent rc file or the environment hid its .pnp.js, so agent mode skipped the package as "not installed" and hosted/vendored lost the PnP warning. A loader beside a classic lock now always counts. - yarn berry also reads the home folder's rc file and honours YARN_RC_FILENAME. With nodeLinker: node-modules set only there, vendor refused an installed node-modules project as PnP. The linker lookup now reads the renamed rc file everywhere and falls back to the home rc, on disk and in the in-memory lock view. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
A memory snapshot is the repository alone, so its PnP decision must not take the scanning host's YARN_NODE_LINKER, YARN_RC_FILENAME or home .yarnrc.yml. A worker's home rc could otherwise disown a repo's live PnP loader, and a renamed rc file name could skip the snapshot's own .yarnrc.yml. The memory view now reads only the snapshot's .yarnrc.yml, plus the yarn 1 classic-lock rule. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent] Ready for review at
Generated by Claude Code |
Conflicts (all formatting-only on this branch's side, main's logic kept): - patch/redirect/mod.rs: keep main's new sbt/scala_guidance mods; gradle already declared. - patch/redirect/poetry.rs: take main's rewrite_poetry_lock_all/LockStep loop. - tests/upstream_restore_golden.rs: take main's #410 sole-pin round-trip assertions. Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
|
bugbot run Generated by Claude Code |
The hosted in-memory engine built each root's MemoryProject from the root's own files only, and the memory flavor probe read only the root's .yarnrc.yml. On disk, yarn_node_linker walks every rc file above the project, the closest one that sets nodeLinker winning. A nested yarn root (apps/web) whose stale .pnp.* loader was disowned by a nodeLinker: node-modules in a repository-level .yarnrc.yml was therefore still refused as Plug'n'Play in memory (Bugbot on b6018c5). - select_paths fetches the .yarnrc.yml files strictly above a root that holds a PnP loader. - project_for hands those texts to the root's MemoryProject, nearest first (set_ancestor_yarnrcs). - The memory probe takes the root rc's nodeLinker, else the nearest ancestor's. The host's env and home rc stay out, as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
Conflict in formats/pnpm/mod.rs: took main's is_shrinkwrap_lock helper (#909); the branch side was only a rustfmt reflow. Main moved yarnrc_scalar to formats::yarn::berry_gates (#657), so the PnP linker readers in pkg_managers and the in-memory view now call it there. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
|
bugbot run Generated by Claude Code |
yarn_berry_vendor_preflight gated on the read-only flavor probe, which accepts a yarn berry project configured for Plug'n'Play (nodeLinker: pnp, or unset) until `yarn install` writes a .pnp.* loader. The takeover then restored the hosted pin before vendor_npm_any refused the project as PnP, relying on the group-commit savepoint (wet) or takeover_dry_refusal (dry) to undo it. Run the forward-vendoring probe first, as vendor_npm_any does, so the takeover refuses before it touches the hosted wiring. Core fixtures that rewrote .yarnrc.yml without nodeLinker now keep `nodeLinker: node-modules`, so they still exercise the gate they name. Bugbot finding on 641af6a (#978). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
bugbot run |
There was a problem hiding this comment.
✅ 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 e03e972. Configure here.
|
[agent] Blocked: CI on What changed this pass:
Bugbot: ran on Generated by Claude Code |
Resolve conflicts with #978 (yarn nodeLinker-aware PnP detection) and #1073 (vlt package.json workspaces fallback): - npm_flavor.rs: the single ProjectView router keeps main's stale-loader rule (#975) through live_pnp_marker_with, with the configured linker supplied by the new ProjectView::yarn_node_linker (disk probe on disk, the repository's own .yarnrc.yml chain in memory, as main's in-memory copy did). main's in-memory router copy in view.rs stays deleted. - governing_root.rs: workspace-root locks still come from the governing table's Npm/Yarn/Bun families, plus main's vlt-lock.json fallback when vlt.json declares no workspaces. - hosted/memory tests import detect_npm_lock_flavor_in from npm_flavor. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
LLM Description written by Claude Code:claude-opus-5-5
Fixes #975
Fixes #539
Root cause
Every "is this a yarn Plug'n'Play project" decision keyed on whether a
.pnp.*loader file exists, never on the linker yarn is configured to use. That covered the agent crawler (crawlers/pkg_managers.rs::detect_npm_pkg_manager), the vendored flavor probe andvlt_routes(vendor/npm_flavor.rs) and the in-memory lock-inventory view (vendor/lock_inventory/view.rs)..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: a Yarn 2 → Yarn 4 migration that switched tonodeLinker: node-modulesorpnpmkeeps the old.pnp.js, and yarn 4 never deletes it. socket-patch refused the project as PnP even though packages are innode_modules/. Agent and vendored runs exited 1, and hosted printed a false "npm dependencies were NOT scanned".nodeLinker: pnp, or unset, which is berry's default) has no loader yet. Vendored mode wired it, then refused every re-run onceyarn installwrote.pnp.cjs.Fix
pkg_managers::yarn_node_linkerreturns the linker yarn resolves:YARN_NODE_LINKERfirst, else the nearest.yarnrc.ymlat or above the project that setsnodeLinker.live_pnp_marker/live_pnp_marker_withcount a loader file only while that linker ispnpor unset. Every formerPNP_MARKERScheck goes through them: the crawler, the vendored probe,vlt_routesand the memory view. That view reads the snapshot's root.yarnrc.yml.npm_flavor::detect_vendorable_npm_flavoris the forward-vendoring probe. It also refuses (vendor_yarn_berry_unsupported) a yarn berry project whose configured linker ispnp, explicit or by default, before any loader exists.vendor_npm_any,preflight_packagesandlock_text_refusalsuse it, so takeovers refuse before they revert anything. Read-only paths keep the plain probe:vendor --check,--revert, VEX and the hosted lock inventory.file:wiring does work under PnP) would be a separate enhancement for a maintainer to decide. The compatibility doc now says PnP follows the configured linker.utils::digest. Main'sproduction_digests_go_through_the_helpersguard is red without it. The port is a no-op once Route Gradle digests through utils::digest #878 lands.Per-issue tests (red on main → green here)
e2e_safety_yarn_pnp::stale_pnp_loader_under_non_pnp_linker_applies(node-modules and pnpm linkers)yarn_pnp_unsupportedin_process_vendor::berry_stale_pnp_loader_under_non_pnp_linker_vendorspkg_managers::stale_pnp_loader_under_non_pnp_linker_is_not_pnp,npm_flavor::stale_pnp_loader_under_non_pnp_linker_is_not_refused,view::memory_flavor_probe_follows_the_disk_decision_tablein_process_vendor::berry_lock_only_pnp_project_refused_up_front(explicitpnp, rc withoutnodeLinker, no rc; lock-only and after install; nothing written)npm_flavor::vendorable_probe_refuses_berry_configured_for_pnpnpm_flavor::pnpm_pnp_layout_refuses_under_a_non_pnp_yarn_linkernode-linker=pnptreepnp_loader_under_explicit_pnp_linker_still_refuses,pkg_managers::pnp_loader_counts_under_pnp_or_unset_linker,yarn_node_linker_follows_yarn_precedenceLocal evidence
cargo fmt --all -- --check: clean.cargo clippy --workspace --all-features -- -D warnings: clean.cargo test --workspace --all-features: everything passes except 12 write-failure tests acrosscovgap_commands_vendor,in_process_redirect,repairand the core lib. Those tests need a write to be denied, and they cannot deny one when run as root, which the sandbox is (uid 0). They are unrelated to this diff, and CI (non-root) does not fail them.scripts/yarn-berry-vex-matrix.sh 4.12.0: all four suites pass (e2e_redirect_yarn_berry_build15,e2e_vendor_yarn_berry_build18,e2e_yarn4_pnpm_linker_build19,e2e_yarn4_workspaces_build17). In the legacy-refusal suite the yarn 3 cells pass; the two yarn 2.4.3 cells could not fetch yarn 2.4.3 through this sandbox's network policy. CI runs them.Follow-up: bug-hunt regressions (
54494cc)The yarn bug-hunt routines found two regressions against main on
59eac7e(see the comments on #975 and #539). Both are fixed here:installConfig.pnpwith a classicyarn.lockhas nonodeLinker, and yarn 1 reads neither.yarnrc.ymlnorYARN_NODE_LINKER. A berrynodeLinker: node-modulesfrom a parent rc file or the environment hid the.pnp.js, so agent mode skipped the package as "not installed" and hosted and vendored lost the PnP warning. Nowpkg_managers::effective_yarn_linkertreats a loader beside a classic lock as always live. The disk probe (live_pnp_marker) and the in-memory lock view both use it.YARN_RC_FILENAMEwere ignored. yarn berry reads the home folder's rc file after the project-side ones, andYARN_RC_FILENAMErenames the rc file it looks for. With the linker set only in one of those places,vendorrefused an installed node-modules project as PnP. NowYarnEnvcarriesYARN_NODE_LINKER,YARN_RC_FILENAMEand the home folder.yarn_node_linkerwalks the renamed rc file up the ancestors and then falls back to the home rc. The in-memory lock view (MemoryProject, a repository snapshot) reads only the snapshot's own.yarnrc.yml, never the scanning host's env or home rc (Bugbot finding on54494cc, fixed in8ee4c54).59eac7eYARN_NODE_LINKER)e2e_safety_yarn_pnp::yarn1_pnp_loader_refuses_despite_berry_linker_settings,pkg_managers::yarn1_pnp_loader_ignores_berry_linker_settings,view::memory_flavor_probe_follows_the_disk_decision_table(yarn 1 case)e2e_safety_yarn_pnp::stale_pnp_loader_under_home_rc_linker_applies,npm_flavor::vendorable_probe_follows_home_rc_and_rc_filename,pkg_managers::yarn_node_linker_reads_home_rc_and_rc_filenameyarn_pnp_unsupported)YARN_RC_FILENAMEnpm_flavor::vendorable_probe_follows_home_rc_and_rc_filename,pkg_managers::yarn_node_linker_reads_home_rc_and_rc_filenameLocal evidence for
54494cc:cargo fmt --all -- --checkandcargo clippy --workspace --all-features -- -D warnings: clean.cargo test -p socket-patch-core --lib: 5255 passed. The 4 that fail need a write to be denied, which can't happen as uid 0 in this sandbox:copy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,pypi_poetry::wire_write_failure_*andpypi_requirements::wire_failure_rolls_back_*.e2e_safety_yarn_pnp: 40/40.in_process_vendor: 118/118.cli_parse_vendor: 32/32.cargo test --workspacebuild ran out of this sandbox's disk allowance (target/ reached 30G), so CI is the full-suite gate for this push.🤖 Generated with Claude Code
https://claude.ai/code/session_01GK9XtmEPwV6tL1bvzkj9ks
Note
Medium Risk
Changes which Yarn projects accept patches or vendored wiring; mis-detection could skip needed fixes or refuse/apply incorrectly, though behavior is heavily covered by new e2e and vendor tests.
Overview
Yarn Plug'n'Play detection now follows the configured
nodeLinker(and related yarn rc/env precedence), not merely the presence of a stale.pnp.*loader. Migrated Yarn 2→4 projects onnode-modulesorpnpmare no longer misclassified as PnP for apply, vendor, and hosted paths; lock-only Berry projects configured for PnP (explicit or default) are refused up front in vendored mode instead of wiring first and failing later. Yarn 1 projects with classic locks still treat an existing loader as live PnP even when Berry linker settings exist elsewhere.This diff chunk is largely
cargo fmtacrosssocket-patch-cliplus new regression tests ine2e_safety_yarn_pnp.rs(stale loader + home rc + Yarn 1 controls) andin_process_vendor.rs(stale loader vendors, lock-only PnP refusal, takeover preflight cases for explicit/default PnP linkers).Reviewed by Cursor Bugbot for commit e03e972. Configure here.