Skip to content

Fix yarn PnP detection ignoring nodeLinker (#975, #539) - #978

Open
Mikola Lysenko (mikolalysenko) wants to merge 7 commits into
mainfrom
agent/fix-yarn-pnp-linker-detect
Open

Mikola Lysenko (mikolalysenko) wants to merge 7 commits into
mainfrom
agent/fix-yarn-pnp-linker-detect

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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 and vlt_routes (vendor/npm_flavor.rs) and the in-memory lock-inventory view (vendor/lock_inventory/view.rs).

Fix

  • pkg_managers::yarn_node_linker returns the linker yarn resolves: YARN_NODE_LINKER first, else the nearest .yarnrc.yml at or above the project that sets nodeLinker. live_pnp_marker / live_pnp_marker_with count a loader file only while that linker is pnp or unset. Every former PNP_MARKERS check goes through them: the crawler, the vendored probe, vlt_routes and the memory view. That view reads the snapshot's root .yarnrc.yml.
  • npm_flavor::detect_vendorable_npm_flavor is the forward-vendoring probe. It also refuses (vendor_yarn_berry_unsupported) a yarn berry project whose configured linker is pnp, explicit or by default, before any loader exists. vendor_npm_any, preflight_packages and lock_text_refusals use it, so takeovers refuse before they revert anything. Read-only paths keep the plain probe: vendor --check, --revert, VEX and the hosted lock inventory.
  • Decision on 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: I chose to refuse up front, because docs/ecosystems.md and the yarn-berry compatibility doc promise "PnP refused". Accepting PnP in vendored mode (the 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.
  • CI: this branch ports open PR Route Gradle digests through utils::digest #878, which routes Gradle/JVM/Maven digests through utils::digest. Main's production_digests_go_through_the_helpers guard 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)

Issue Test Before
#975 (agent) e2e_safety_yarn_pnp::stale_pnp_loader_under_non_pnp_linker_applies (node-modules and pnpm linkers) exit 1 yarn_pnp_unsupported
#975 (vendored) in_process_vendor::berry_stale_pnp_loader_under_non_pnp_linker_vendors exit 1
#975 (unit / memory view) pkg_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_table —
#539 in_process_vendor::berry_lock_only_pnp_project_refused_up_front (explicit pnp, rc without nodeLinker, no rc; lock-only and after install; nothing written) lock-only run exited 0 and wired
#539 (unit) npm_flavor::vendorable_probe_refuses_berry_configured_for_pnp —
Bugbot finding npm_flavor::pnpm_pnp_layout_refuses_under_a_non_pnp_yarn_linker vendored a pnpm node-linker=pnp tree
control pnp_loader_under_explicit_pnp_linker_still_refuses, pkg_managers::pnp_loader_counts_under_pnp_or_unset_linker, yarn_node_linker_follows_yarn_precedence —

Local 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 across covgap_commands_vendor, in_process_redirect, repair and 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_build 15, e2e_vendor_yarn_berry_build 18, e2e_yarn4_pnpm_linker_build 19, e2e_yarn4_workspaces_build 17). 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.
  • npm/pypi/gem wrappers: not affected (no PnP logic there).

🤖 Generated with Claude Code

https://claude.ai/code/session_01GK9XtmEPwV6tL1bvzkj9ks


Note

Medium Risk
Changes which Yarn Berry projects are treated as PnP across apply, vendor, scan, and hosted flows—misread linker config could patch or wire projects that should be refused, or vice versa.

Overview
Yarn Plug'n'Play is now decided from the configured nodeLinker (and YARN_NODE_LINKER), not merely from a leftover .pnp.* file on disk. Stale loaders after migrating to node-modules or pnpm no longer block apply, vendor, or npm-family scans; live PnP projects configured with pnp (or berry’s default) are still refused where documented, including lock-only checkouts before yarn install creates a loader.

Core behavior is centralized in yarn_node_linker / live_pnp_marker (crawler, vendored probes, lock inventory) and detect_vendorable_npm_flavor for forward vendoring paths. This diff adds e2e and in-process vendor tests for #975 and #539, plus repo-wide rustfmt / import-order tweaks across CLI commands and tests (no functional edits in those formatting-only hunks).

Reviewed by Cursor Bugbot for commit 59eac7e. Configure here.


Generated by Claude Code

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
Ports #878 so this branch passes the digest guard test that is red
on main: the Gradle cache, JVM jar and Maven sidecar code hashed
bytes inline instead of through utils::digest. No-op once #878
lands.

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

Copy link
Copy Markdown
Collaborator Author

[agent] coverage failed on the earlier heads in utils::digest::tests::production_digests_go_through_the_helpers. That guard is red on main too: Gradle/JVM/Maven code hashes inline. The failure isn't from this PR, so I ported the fix from open PR #878 (commit "Route Gradle and Maven digests through helpers"). It becomes a no-op once #878 lands. The clippy failure on 257d86a was this PR's own (a needless borrow) and is fixed in 369e6a3.


Generated by Claude Code

@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 7, 2026 03:18
@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/vendor/npm_flavor.rs
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
@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 59eac7e. Configure here.

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

Copy link
Copy Markdown
Collaborator Author

[agent] Ready for review at 59eac7e.

  • CI: 541/541 green (6 skipped).
  • Bugbot: reviewed 59eac7e, nothing unresolved. Its one finding on 8df7bc6 was real: a non-pnp yarn linker (an ancestor .yarnrc.yml or YARN_NODE_LINKER) skipped the pnpm node-linker=pnp refusal. Fixed in 59eac7e, with a regression test.
  • For the reviewer: yarn PnP is now decided from the configured nodeLinker, not from whether .pnp.* loader files exist. The pnpm PnP refusal runs before that check.

Generated by Claude Code

This branch has not been deployed

No deployments
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

2 participants