Skip to content

Fix berry pins carrying registry-only node-gyp dep (#737) - #1319

Merged
Mikola Lysenko (mikolalysenko) merged 5 commits into
mainfrom
agent/v5-berry-node-gyp
Oct 9, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 5 commits into
mainfrom
agent/v5-berry-node-gyp

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Fixes #737

Summary

Yarn Berry hosted and vendored pins of native-addon packages (nan, bufferutil, utf-8-validate, node-addon-api, …) no longer carry the registry entry's implicit node-gyp: "npm:latest" dependency. The pin also drops the lock entries that only node-gyp reached, so the lock matches what yarn itself writes. Before this, vendored yarn install --immutable and hardened hosted installs failed with YN0028.

Root cause

Yarn's npm resolver (NpmSemverResolver) adds node-gyp: "npm:latest" to a registry entry when one of its scripts mentions node-gyp and the manifest doesn't declare node-gyp itself. The registry injects install: node-gyp rebuild for any package that ships a binding.gyp. Yarn's tarball and file: resolvers read the tarball's package.json as is and add nothing.

Both pin writers (hosted patch/redirect and vendored vendor/yarn_berry_lock) built the pinned entry from the registry entry's body through formats::yarn::berry_entry::render_pinned_entry, so the dependency was copied over. On the next resolution yarn dropped it, together with the whole node-gyp subtree (about 20 entries).

Fix

  • berry_entry::Pin now takes the served tarball's manifest, not just its bin map. The renderer drops the implicit node-gyp: "npm:latest" line unless that manifest declares node-gyp in dependencies or optionalDependencies. The registry entry's other dependency maps are kept, since they already include yarn's packageExtensions normalization.
  • New formats/yarn/berry_prune.rs starts from the dependency edges the pin cut. It drops each entry, or each descriptor of a shared key, that nothing else references. It errs on keeping:
    • workspace, patch: and portal: entries stay;
    • packages named by a resolutions selector stay;
    • a descriptor stays if any other entry mentions it, including inside a patch: locator.
  • Hosted pin: an entry that carries the implicit dependency now queues the served-manifest fetch, the same way bin: entries already do (Yarn berry vendored and hosted pins copy the registry entry's bin: paths, but yarn re-reads them from the tarball (./dist/bin/uuid), so vendored installs and hardened hosted installs fail YN0028 for packages like uuid and acorn #718). Pruned entries are reported as redirect_yarn_berry_entry_pruned edits.
  • Vendored pin: pruned entries are recorded as yarn_berry_lock_entry_pruned wiring, with the entry's lines in original. vendor --revert puts them back where yarn sorts them, byte-exact.
  • Hosted rollback / remove: the restore now reads whether the registry version document implies the implicit dependency (NpmDist::node_gyp).
    • While the lock still has a node-gyp@npm:latest entry, the restore adds the line back, and the rollback is byte-exact.
    • Otherwise it warns yarn_berry_node_gyp_unresolved: run yarn install once. Hosted mode keeps no ledger, so it can't rebuild the pruned subtree.

Note: the restore side touches patch/redirect/upstream/npm.rs restore_berry (a small block just before the re-key). Another agent is changing the same function for #1131 / #1017 / #1203, so whichever PR lands second needs a trivial rebase.

Tests (red → green)

Issue Test
#737 renderer formats::yarn::berry_entry::tests::issue_737_pin_drops_the_registry_implicit_node_gyp, registry_implicit_node_gyp_round_trips
#737 prune walk formats::yarn::berry_prune::tests::*
#737 hosted pin patch::redirect::tests::issue_737_pin_drops_the_implicit_node_gyp_and_the_subtree_only_it_reached. Fixtures are real yarn 4.12.0 output for nan@2.22.0 + bufferutil@4.0.8 pinned through resolutions, matched byte for byte. With nan alone, bufferutil keeps node-gyp alive; with both pinned, 20 entries are pruned.
#737 vendored + revert vendor::yarn_berry_lock::tests::issue_737_implicit_node_gyp_and_its_subtree_are_dropped_and_reverted
#737 CLI pin + rollback in_process_redirect::yarn_berry_pin_drops_the_implicit_node_gyp_and_rollback_restores_it. Shared case: rollback is byte-exact. Sole case: the warning fires.

Red: with the drop disabled in the renderer, all three core #737 tests fail. I also checked by hand with yarn 4.12.0 that a file: tarball resolution of nan gets no node-gyp dependency.

Commands run

  • cargo fmt --all -- --check: clean apart from a diff at upstream/mod.rs:917 that is already on main and isn't touched here
  • cargo clippy --workspace --all-features -- -D warnings: clean
  • cargo test -p socket-patch-core --lib: all pass
  • SOCKET_PATCH_YARN_E2E_REQUIRED=1 SOCKET_PATCH_YARN_BERRY_VERSION=4.12.0 cargo test -p socket-patch-cli --test e2e_redirect_yarn_berry_build --test e2e_vendor_yarn_berry_build --test e2e_yarn4_pnpm_linker_build --test e2e_yarn4_workspaces_build --test in_process_redirect --test in_process_vendor --test scan --test mode_migration_npm: all pass

🤖 Generated with Claude Code


Note

Medium Risk
Changes Yarn Berry lockfile rewrite, prune, and rollback behavior for native-addon packages; incorrect pruning or restore could break installs, but behavior is heavily fixture-tested and aligned with Yarn 4 output.

Overview
Fixes #737: Yarn Berry hosted and vendored pins no longer copy the registry-only implicit node-gyp: "npm:latest" dependency onto tarball/file: lock entries, and they prune lock subtrees that become unreachable when that edge is removed—matching what Yarn writes so yarn install --immutable stops failing with YN0028.

Pin path: berry_entry::Pin now takes the served tarball manifest and strips the implicit node-gyp line unless the manifest declares it; new berry_prune walks cut dependency edges and drops unreferenced entries (with conservative keeps for workspaces, patch:/portal:, and resolutions targets). Hosted redirect and vendored Berry both run pruning and record *_entry_pruned edits/wiring; vendored revert restores pruned stanzas or warns vendor_lock_entry_pruned_kept when the lock drifted.

Rollback/remove: upstream npm restore detects registry-implied node-gyp (NpmDist::node_gyp) and re-injects the dependency when node-gyp@npm:latest is still in the lock; otherwise it emits yarn_berry_node_gyp_unresolved (remedy: run yarn install once). CLI contract and ecosystem docs document the new warning codes.

Tests add Berry lock fixtures, unit tests for renderer/prune, redirect/vendor tests, and an in-process CLI pin+rollback scenario.

Reviewed by Cursor Bugbot for commit ebb3940. Configure here.


Generated by Claude Code

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Yarn's npm resolver gives a registry entry whose scripts run node-gyp
(any package with a binding.gyp: nan, bufferutil, utf-8-validate, ...)
an implicit `node-gyp: "npm:latest"` dependency. The tarball and file:
resolvers never add it. Both berry pin writers copied it from the
registry entry, so vendored installs and hardened hosted installs
failed `yarn install --immutable` with YN0028.

Hosted and vendored pins now drop the dependency unless the tarball's
own package.json declares node-gyp, and drop the lock entries only it
reached (yarn deletes them on the next install). Fixtures are yarn
4.12.0's own output for nan and bufferutil, matched byte for byte.

Vendored mode records the pruned entries so `vendor --revert` restores
the lock byte for byte. Hosted rollback adds the dependency back while
the lock still resolves node-gyp; otherwise it warns
`yarn_berry_node_gyp_unresolved` (run `yarn install` once).

Fixes #737

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The golden corpus walks every directory under tests/fixtures, so the
yarn lock fixtures move to flat files.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 9, 2026 18:00
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review

@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/berry_prune.rs
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


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 ebb3940. Configure here.

@mikolalysenko
Mikola Lysenko (mikolalysenko) added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 9ef59df Oct 9, 2026
53 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/v5-berry-node-gyp branch October 9, 2026 22:12
Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Oct 9, 2026
Resolve conflicts with #1319 (berry implicit node-gyp) and #1283 (Bun
user registry config):

- NpmDist carries both `bin` (#1131) and `node_gyp` (#737).
- #1319 replaced Pin's `bin` with `manifest`; the url-pin restore now
  passes a manifest holding only the version document's bin (and a
  declared node-gyp so render_pinned_entry keeps the entry's
  dependencies), and runs before the implicit node-gyp re-add so that
  re-add is not undone.
- CLI_CONTRACT.md npm-family paragraph merged word by word: keeps the
  berry bin restore note and the Bun user-config registry rules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants