Skip to content

Hosted yarn berry pin of a catalog: dependency keys resolutions by the resolved npm: range, so every yarn install --immutable fails YN0028 (regression from #465) #632

Description

[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).

Summary

Since #465 (203e092), a hosted Berry pin is a root package.json resolutions entry keyed by the locked descriptor (left-pad@npm:^1.3.0), plus the lock entry re-keyed left-pad@<url>. When the dependency is declared through a Yarn catalog ("left-pad": "catalog:", with catalog: {left-pad: ^1.3.0} in .yarnrc.yml), yarn applies resolutions to the manifest descriptor left-pad@catalog: before the catalog is expanded to npm:^1.3.0. So the left-pad@npm:^1.3.0 selector never matches. Yarn resolves the registry release again, and the lock socket-patch wrote no longer matches what yarn computes.

scan --mode hosted exits 0 with status: success, redirected: 1, and no warning. Then:

  • yarn install --immutable (in place or in a fresh checkout) fails with YN0028 ("The lockfile would have been modified"). Yarn wants to turn left-pad@<url> back into left-pad@npm:1.3.0.
  • A mutable yarn install silently installs the unpatched registry bytes and drops the pin from yarn.lock. The stale resolutions entry stays in package.json.
  • Re-running scan --mode hosted reports redirected: 1 again and writes the same broken pin, so the loop never converges.

Release 4.0.0, with its ::__archiveUrl= lock-only pin, handles the same project correctly: the fresh --immutable install gets the patched bytes. This is a regression.

vex does the right thing after the mutable install. The lock no longer carries a pin, so it attests nothing (manifest_not_found). There's no false attestation.

Impact

  • Any Yarn ≥ 4.10 project that uses catalogs, which are increasingly the norm in monorepos, can't use hosted mode. CI (enableImmutableInstalls is on by default under CI) goes red immediately after the hosted commit.
  • Local devs running plain yarn install silently get unpatched code. The command reports success throughout.

Repro (Linux, yarn 4.18.1, node-modules linker)

mkdir c && cd c
echo '{"name":"c","private":true,"dependencies":{"left-pad":"catalog:"}}' > package.json
printf 'nodeLinker: node-modules\nenableGlobalCache: false\ncatalog:\n  left-pad: ^1.3.0\n' > .yarnrc.yml
yarn install
socket-patch scan --mode hosted --json --yes --api-url <mock> --org org --api-token x --patch-server-url <mock>
#  -> status success, redirected 1, warnings []
#  package.json gains: "resolutions": {"left-pad@npm:^1.3.0": "<mock>/patch/npm/left-pad/1.3.0/tok/<uuid>/left-pad-1.3.0.tgz"}
yarn install --immutable
#  ➤ YN0028: -"left-pad@<url>":
#  ➤ YN0028: +"left-pad@npm:^1.3.0":
#  ➤ YN0028: The lockfile would have been modified by this install, which is explicitly forbidden.
yarn install && head -c 12 node_modules/left-pad/index.js   # unpatched registry bytes

The patch API was a local mock serving a granted yarn-berry-zip artifact whose yarnBerry10c0 was bootstrapped with a real yarn resolutions: file: install, the same approach as crates/socket-patch-cli/tests/e2e_redirect_yarn_berry_build.rs.

Which selector yarn honours for a catalog dependency (mutable install with each hand-written resolutions key → URL):

resolutions key installed bytes
left-pad@npm:^1.3.0 (what socket-patch writes) unpatched, lock entry left-pad@npm:^1.3.0
left-pad@catalog: patched, lock entry left-pad@<url>
left-pad (bare) patched, lock entry left-pad@<url>

A workspace mixing catalog: (root and member b) with a named catalog:legacy (member a, 1.3.0) fails the same way. socket-patch writes left-pad@npm:1.3.0 and left-pad@npm:^1.3.0, and yarn wants a merged "left-pad@npm:1.3.0, left-pad@npm:^1.3.0" registry entry.

Expected vs actual

  • Expected: docs/ecosystems.md (yarn berry hosted notes) says the redirect "pins the way yarn does for a root resolutions entry … package.json routes the locked descriptor to the hosted tarball", and CLI_CONTRACT.md says a dep counts as redirected only when the pin actually lands. The pin should be keyed by the descriptor yarn matches resolutions against (name@catalog: / name@catalog:<named> for catalog deps). Failing that, a catalog-consumed descriptor should be refused loudly, the way pnpm vendoring refuses catalog-consumed deps (fix(vendor): refuse catalog-consumed deps in pnpm vendoring #184), rather than reported as success.
  • Actual: exit 0, redirected: 1, no warning. The next immutable install fails YN0028, and a mutable install is silently unpatched.

Matrix

OS yarn socket-patch Result
Linux 4.18.1 main 045d7ec (3 runs: minimal root, workspace + named catalog, re-scan loop) fail
Linux 4.12.0 main 045d7ec fail
Linux 4.18.1 release 4.0.0 (npm) pass (fresh --immutable patched; ::__archiveUrl= pin)
Linux < 4.10 — n/a (no catalogs)
macOS / Windows — — untested; the selector choice is OS-independent. No probe branches this run: the sandbox git proxy still drops branch deletes.

First bad commit: 203e092 (#465, "Fix yarn berry hosted pin leaking npm auth (#404)"), which introduced the resolutions pin. Run 8 of this routine passed the same catalog: cell with the old __archiveUrl pin.

Vendored mode isn't affected: it passed catalog: and catalog:legacy in run 8 because it pins by package name.

Suspect code

  • crates/socket-patch-core/src/patch/redirect/mod.rs:4081, in berry_resolutions_pin: selectors are built as format!("{name}@{r}") from the lock entry's npm ranges (key_ranges). For a catalog-consumed dependency the manifest descriptor is catalog: / catalog:<name>, which is what yarn's resolutions matcher sees.
  • No catalog handling exists anywhere in patch/redirect/mod.rs, so the hosted gate has no catalog refusal either.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions