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
Activity
- addedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:yarn-berryYarn Berry (2+)Yarn Berry (2+)
on Oct 7, 2026 mikolalysenko commented
on Oct 7, 2026 CollaboratorAuthorMore actions[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 commented
on Oct 8, 2026 CollaboratorAuthorMore actions[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).Point 4.0.2 4.18.1 Status 3. Installed-tree vexattests the unpatched member copynot attested; warns patched_ref_unattributable("that copy stays UNPATCHED")same fixed by #1033 2. rollbackexit 0, then fresh--immutableYN0028now exit 1, status: error, nothing written; fresh--immutablepasses, because the lock still holds the pinsame now fail-closed 1. Re-scan dead end scan --mode hostedexit 0,redirect_yarn_berry_ambiguous_entry("runyarn installonce to dedupe the lock")same still reproduces 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 commented
on Oct 9, 2026 CollaboratorAuthorMore actions[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):mkdir -p ws/packages/a && cd ws echo '{"name":"a","version":"1.0.0"}' > packages/a/package.json echo '{"name":"app","version":"1.0.0","private":true,"workspaces":["packages/*"],"dependencies":{"left-pad":"^1.3.0"}}' > package.json printf 'nodeLinker: node-modules\n' > .yarnrc.yml; touch yarn.lock; yarn install socket-patch scan --mode hosted; yarn install (cd packages/a && yarn add left-pad@1.3.0) # or: yarn add lp@npm:left-pad@1.3.0 socket-patch rollback # exit 0, "Restored pkg:npm/left-pad@1.3.0 …" # fresh checkout of package.json + yarn.lock + .yarnrc.yml, cold cache: yarn install --immutable # YN0028
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@npm:1.3.0": version: 1.3.0 resolution: "left-pad@npm:1.3.0" "left-pad@npm:^1.3.0": version: 1.3.0 resolution: "left-pad@npm:1.3.0"Binary yarn member left-pad@1.3.0:rollback/remove left-padlp: npm:left-pad@1.3.0:rollback/remove left-padea097144.18.1 exit 1, nothing written (fail-closed) exit 1, nothing written 4d06019b(#1058)4.18.1 exit 1 exit 1 b17a114a(parent of #1008)4.18.1 exit 1 exit 1 f3c6313a(#1008)4.18.1 exit 0, then YN0028 exit 0, then YN0028 a80b89e,21536b294.18.1 exit 0, then YN0028 exit 0, then YN0028 9ab72d4(main)4.0.2, 4.18.1 exit 0, then YN0028 exit 0, then YN0028 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
[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).
Summary
A hosted berry pin is a root
resolutionsselector 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 onlpx@~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: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(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.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--immutableinstall rejects.Repro (local registry + mock patch API; synthetic
lpx@1.0.0)Expected vs actual
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.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.OS × version
Tested on main
05ecc6e, and on PR #1033 head314038bfor the re-scan and VEX columns. Not bisected. Releases ≤4.0.0 pinned through an::__archiveUrl=locator instead ofresolutions.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.