Repository navigation
Yarn berry hosted pin can't cover a descriptor added after pinning: re-scan refuses with an impossible "dedupe" remedy, and rollback reports success but leaves a lock every yarn install --immutable rejects (YN0028) #1082
Description
[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).
Summary
A hosted berry pin is a root resolutions selector for each descriptor that existed when you ran the scan (for example "lpx@npm:^1.0.0": "<socket url>"). If someone later adds the same package under a different range, the selector doesn't cover it. That happens when a workspace member adds "lpx": "1.0.0", or when a new dependency depends on lpx@~1.0.0. Yarn then locks a second, separate registry entry "lpx@npm:1.0.0" beside the Socket URL entry, and installs that copy unpatched. Three things go wrong after that:
- A re-scan can't fix it.
scan --mode hostedexits 0 and pins nothing (redirect_yarn_berry_ambiguous_entry): "yarn.lock holds 2 separate entries resolving lpx@1.0.0; runyarn installonce to dedupe the lock, then re-run". Neitheryarn installnoryarn dedupemerges the two entries, because theresolutionspin is what keeps them apart. The suggested fix can't work, and nothing else is offered. - Rollback breaks the lock.
rollback(andremove) exit 0 with "Restored pkg:npm/lpx@1.0.0", but they write back two blocks with the same resolution ("lpx@npm:1.0.0"and"lpx@npm:^1.0.0", bothresolution: "lpx@npm:1.0.0"). Yarn merges those into"lpx@npm:1.0.0, lpx@npm:^1.0.0", so every freshyarn install --immutablefails with YN0028. - Lock-only
vexon main attestsnot_affectedwhilepackages/b/node_modules/lpxis unpatched. Open PR Stop VEX attesting over yarn PnP, pnpm bundled and deno.lock copies #1033 already fixes this part (its berry same-lock rule; I checked its head314038b: lock-only vex now drops the ref with the "stays UNPATCHED" warning). But Stop VEX attesting over yarn PnP, pnpm bundled and deno.lock copies #1033 says a re-run ofscan"rewires every copy", and for berry hosted that isn't true (point 1). With Stop VEX attesting over yarn PnP, pnpm bundled and deno.lock copies #1033 merged, the project loses VEX and still has no way to re-pin.
Impact
This is a normal workflow: someone adds a dependency after the hosted pin. Afterwards one copy of the patched package installs unpatched, the scan reports nothing it can do (exit 0), and the documented undo (rollback) leaves a lockfile that CI's --immutable install rejects.
Repro (local registry + mock patch API; synthetic lpx@1.0.0)
mkdir -p proj/packages/b && cd proj
echo '{"name":"root","version":"1.0.0","private":true,"workspaces":["packages/*"],"dependencies":{"lpx":"^1.0.0"}}' > package.json
printf 'nodeLinker: node-modules\n' > .yarnrc.yml
yarn install
socket-patch scan --mode hosted --yes # pins lpx@npm:^1.0.0 → socket url (resolutions + lock)
echo '{"name":"b","version":"1.0.0","dependencies":{"lpx":"1.0.0"}}' > packages/b/package.json
yarn install # adds a separate "lpx@npm:1.0.0" registry entry
head -1 packages/b/node_modules/lpx/index.js # unpatched
socket-patch scan --mode hosted --yes # exit 0: "holds 2 separate entries … run `yarn install` once to dedupe"
yarn install && yarn dedupe && socket-patch scan --mode hosted --yes # same refusal, still 2 entries
socket-patch rollback # exit 0 "Restored …"
# fresh checkout of package.json/yarn.lock/.yarnrc.yml:
yarn install --immutable # YN0028 (lock would merge the two entries)Expected vs actual
- Expected: a re-scan pins the new descriptor too. The rewriter already pins merged multi-descriptor entries by adding one
resolutionsselector per range, so it can addlpx@npm:1.0.0and fold the registry entry into the URL entry. If it can't, it should refuse with a remedy that works (exit non-zero, or at least name the descriptor to remove). CLI_CONTRACT.md / docs/ecosystems.md describe a re-run ofscanas the way to re-wire a project whose lock drifted. - Expected:
rollbackleaves a lock thatyarn install --immutableaccepts (the bar every other berry rollback cell meets). If it can't produce one, it should fail loudly rather than report success. - Actual: as in the summary.
OS × version
| OS | yarn | re-scan dead end | rollback → YN0028 | lock-only vex attests (main) |
|---|---|---|---|---|
| Linux | 4.0.2 | reproduces (2/2) | reproduces (2/2) | reproduces (2/2); dropped on PR #1033 |
| Linux | 4.18.1 | reproduces (2/2) | reproduces (2/2) | reproduces (2/2); dropped on PR #1033 |
| macOS / Windows | — | untested (the probe branches are blocked) |
Tested on main 05ecc6e, and on PR #1033 head 314038b for the re-scan and VEX columns. Not bisected. Releases ≤4.0.0 pinned through an ::__archiveUrl= locator instead of resolutions.
Suspect code
crates/socket-patch-core/src/patch/redirect/mod.rs:4141-4152:targets.len() > 1givesredirect_yarn_berry_ambiguous_entrywith the dedupe remedy, even when one of the two targets is the existing Socket URL entry and the other is a registry entry whose descriptor just needs another selector.crates/socket-patch-core/src/patch/redirect/upstream/npm.rs:546(restore_berry): it restores the URL entry's keys as their own block next to an existing block with the sameresolution:, and doesn't merge them the way yarn would.crates/socket-patch-core/src/vex/discover/yarn.rs:405-409: a plain registry entry is onlyresolved_elsewhere, not anunpatched_copy(PR Stop VEX attesting over yarn PnP, pnpm bundled and deno.lock copies #1033 changes this).
Backlog review — 2026-10-08
Priority: P1 → P2. Adding a Yarn descriptor after pinning creates a refusal/broken immutable install. Retain the recovery fix; the dangerous VEX portion was addressed separately.
Activity
mikolalysenko commentedon Oct 7, 2026
[agent] Triage:
priority:p1(Yarn berry, npm-family). Not a duplicate. Point 3 (lock-only VEX) is addressed by open PR #1033, as the report notes. Points 1 (the ambiguous-entry refusal atredirect/mod.rs:4141) and 2 (restore_berrynot merging blocks that share a resolution) have no open PR. #962 also ends in YN0028 after rollback, but its cause is a useryarn patch, so I'm not clustering them.Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Re-triage on main
ea09714(after #1033 and #1035). Linux, yarn 4.0.2 and 4.18.1, same repro as the report (workspace member addslpx: 1.0.0after a hosted pin oflpx@npm:^1.0.0).vexattests the unpatched member copypatched_ref_unattributable("that copy stays UNPATCHED")rollbackexit 0, then fresh--immutableYN0028status: error, nothing written; fresh--immutablepasses, because the lock still holds the pinscan --mode hostedexit 0,redirect_yarn_berry_ambiguous_entry("runyarn installonce to dedupe the lock")Point 1 is now a loop. The rollback refusal says: "reconcile the lockfiles (re-run
socket-patch scan --mode hosted) or restore them from version control (git checkout -- package.json yarn.lock)". The re-scan refuses with the dedupe remedy, andyarn install/yarn dedupedon't merge the two entries. So the only way out isgit checkout, which also throws away the member's new dependency. The member copy stays unpatched, and nothing in socket-patch can re-pin it or unwind it.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
[agent] Re-triage on main
9ab72d4(5.0.0): point 2 has regressed.rollbackandremoveexit 0 with "Restored" again, and every freshyarn install --immutablethen fails YN0028. Onea09714(run 31) the same repro refused with exit 1 and wrote nothing. I bisected it tof3c6313a(#1008, "Fix open npm issues"); its parentb17a114astill refuses.The same thing happens when the descriptor added after pinning is an
npm:alias (lp: npm:left-pad@1.3.0), either at the root or in a member.ea09714refused that shape too.Linux, yarn 4.0.2 and 4.18.1 (from
@yarnpkg/cli-dist), node-modules linker, local mock patch API (--api-url/--proxy-url/--patch-server-url):After the rollback, the lock holds two blocks with the same resolution. Yarn would merge them into
"left-pad@npm:1.3.0, left-pad@npm:^1.3.0":, which is why the immutable install rejects the lock:left-pad@1.3.0:rollback/remove left-padlp: npm:left-pad@1.3.0:rollback/remove left-padea097144d06019b(#1058)b17a114a(parent of #1008)f3c6313a(#1008)a80b89e,21536b299ab72d4(main)Likely cause: the #828 part of #1008 (the
Discovery::shadowedlist).contest_within_locks(crates/socket-patch-core/src/vex/discover/mod.rs:827-862) used to drop a ref whose own lock also installs an unpatched copy of the same name@version. It now shadows that ref, soHostedPin::allhands it to rollback/remove. That is right for an npm bundled copy, which sits in its own location. In a berry lock, though, the unpatched sibling is a block that yarn merges with the restored one.restore_berry(crates/socket-patch-core/src/patch/redirect/upstream/npm.rs:735-748) rewrites the pinned block toleft-pad@npm:1.3.0in place and never merges it into the existing block with that resolution (point 2 of the triage above). Before #1008, the contest refusal hid this. Either a berry same-lock sibling should keep refusing, orrestore_berryneeds to merge blocks that share a resolution.Vendored counterpart, same unmerged-block shape: vendor
left-pad, then addlp: npm:left-pad@1.3.0(the alias keeps a registry block,alias_skipped).vendor --revert,rollbackandremove left-padall exit 0, and fresh--immutablefails YN0028 on 4.0.2 and 4.18.1. The pre-vendor entry is restored next to the alias block that resolves to the same locator. The member-descriptor version of this was noted on #759 in run 29.Point 1 (the re-scan dead end) is unchanged.
Generated by Claude Code
mikolalysenko commentedon Oct 11, 2026
[agent] Claiming this issue (cluster of one; root cause: the berry hosted pin and its unwind treat the hosted URL entry and a later registry entry of the same name@version as unrelated blocks, so the re-scan refuses instead of folding the new descriptor into the pin, and
restore_berrywrites back a second block with the sameresolution:that yarn would merge). Branch: agent/fix-berry-hosted-pin-sibling-entry. Claim-ID: 2026-10-11T19:21:02Z-da5509Generated by Claude Code
mikolalysenko commentedon Oct 11, 2026
[agent] Draft PR: #1397
Generated by Claude Code
Fold later yarn berry descriptors into the pin
Prove the #1082 berry flow against a real yarn
Document the berry fold and merge for #1082