Skip to content

Yarn berry vendored and hosted pins put checksum: out of yarn's field order on platform-conditional lock entries (conditions: os=…), so every yarn install --immutable fails YN0028 #697

Description

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

Summary

Yarn 4 writes a lock entry's fields in a fixed order: version, resolution, dependencies, peerDependencies, dependenciesMeta, peerDependenciesMeta, bin, checksum, conditions, languageName, linkType. Platform-specific packages carry a conditions: line (conditions: os=linux & cpu=x64 & libc=glibc), and when yarn reaches them only through optionalDependencies (esbuild → @esbuild/linux-x64, sharp → @img/sharp-linux-x64) it writes the entry without a checksum: line. Both berry pins place the checksum somewhere else:

  • Vendored (vendor / scan --mode vendored) rebuilds the entry as version, resolution, carried sections, checksum, languageName, linkType. conditions: is a carried section, so it lands before checksum:. Every conditional entry is affected, including a direct dependency whose original entry already had a checksum.
  • Hosted (scan --mode hosted), when the original entry has no checksum: line, inserts one directly after resolution: (patch/redirect/mod.rs ~3806). That's before dependencies: / dependenciesMeta: / bin:, so any conditional entry with dependencies (every @img/sharp-* platform package) is affected.

Yarn's immutable check compares the lock text, so the misplaced line alone fails --immutable (YN0028; the diff is the same checksum line moved). scan exits 0 with status: success, and vendor --check reports vendor_check_ok.

Impact

Any project that vendors or hosts a patch for a platform-native package (@esbuild/*, @img/sharp-*, @rollup/rollup-*, @swc/core-*, lightningcss-*, @parcel/watcher-*, …) gets a committed lock that fails every CI yarn install --immutable (yarn turns immutable on by itself in CI). A plain yarn install "fixes" it by reordering the line. That's the opposite of the documented contract that the pin survives a fresh yarn install --immutable --check-cache.

Repro (yarn 4.18.1; local mock patch API serving a README-only patch for @img/sharp-linux-x64@0.33.5, yarnBerry10c0 from a real resolutions: file: bootstrap install)

mkdir p && cd p
echo '{"name":"dd","version":"1.0.0","private":true,"dependencies":{"sharp":"0.33.5"}}' > package.json
printf 'nodeLinker: node-modules\n' > .yarnrc.yml
yarn install            # lock entry "@img/sharp-linux-x64@npm:0.33.5": dependencies / dependenciesMeta / conditions, no checksum
git init -q && git add -A && git commit -qm init
socket-patch scan --mode vendored --json --yes <api flags>; echo $?   # 0, success   (or: --mode hosted)
git clone -q . ../fresh && cp -r .socket ../fresh/ 2>/dev/null; cd ../fresh
yarn install --immutable --check-cache; echo $?                       # 1
# ➤ YN0028: │ +  checksum: 10c0/0ab5bac1…   (where yarn wants it: after dependenciesMeta, before conditions)
# ➤ YN0028: │ -  checksum: 10c0/0ab5bac1…   (where socket-patch wrote it)
# ➤ YN0028: │ The lockfile would have been modified by this install, which is explicitly forbidden.

Lock entry as written (vendored):

"@img/sharp-linux-x64@file:./.socket/vendor/npm/<uuid>/@img/sharp-linux-x64-0.33.5.tgz::locator=dd%40workspace%3A.":
  version: 0.33.5
  resolution: "@img/sharp-linux-x64@file:…"
  dependencies: …
  dependenciesMeta: …
  conditions: os=linux & cpu=x64 & libc=glibc
  checksum: 10c0/0ab5bac1…          # yarn: before `conditions:`
  languageName: node
  linkType: hard

Hosted writes checksum: between resolution: and dependencies: instead.

Expected vs actual

  • Expected: docs/testing/yarn-berry-compatibility.md says the hosted pin is "what yarn itself writes for a root resolutions entry", and that a fresh yarn install --immutable --check-cache "passes … and leaves the lock untouched". Vendored "wires the root package.json resolutions plus the lock's file: entry" as "the exact entry yarn 4 emits" (comment at vendor/yarn_berry_lock.rs:349). Both should put checksum: where yarn does: after bin: / the dependency maps, before conditions:.
  • Actual: the checksum is misplaced, so every immutable install fails YN0028 while socket-patch reports success.

Matrix (Linux, main 045d7ec, Node 22; each cell is a fresh fixture + fresh-checkout yarn install --immutable --check-cache)

yarn fixture vendored hosted
4.0.2 (bare-hex checksums) sharp@0.33.5 (transitive @img/sharp-linux-x64, deps, no checksum) YN0028 YN0028
4.6.0 same YN0028 YN0028
4.12.0 same YN0028 YN0028
4.18.1 same (×2) YN0028 YN0028
4.18.1 esbuild@0.19.12 (transitive @esbuild/linux-x64, no deps, no checksum) YN0028 pass (the line after resolution: happens to be yarn's spot)
4.18.1 direct @esbuild/linux-x64@0.19.12 (checksum present) YN0028 pass (existing line replaced in place)
4.18.1, supportedArchitectures: {os: [darwin], cpu: [arm64]} esbuild@0.19.12 YN0028 pass

Every failing cell's YN0028 diff is only the moved checksum: line. Non-conditional packages (left-pad, debug, ms, …) pass in both modes, which is why the existing suites are green.

Not a regression: release 4.0.0 fails the same sharp cell in both modes (its hosted pin is the older ::__archiveUrl= form, with the same insertion). Release 3.3.0 predates these flags.

Suspect code

  • Vendored: crates/socket-patch-core/src/vendor/yarn_berry_lock.rs:351-360 builds version, resolution, carried…, checksum, languageName, linkType. carried_sections (~1301) treats conditions: as carried, so it's emitted before checksum:. conditions needs to come after the checksum.
  • Hosted: crates/socket-patch-core/src/patch/redirect/mod.rs:3806-3815. The Some(checksum) arm with no existing checksum: line inserts it right after resolution:. It should go before conditions: / languageName: (after bin: and the dependency maps).
  • Related, smaller: hosted rollback (patch/redirect/upstream/npm.rs restore_berry) keeps the inserted checksum: line on an entry that originally had none, so the restored lock isn't byte-identical to the pre-pin lock. Yarn accepts it under --immutable, and the next mutable yarn install removes it again.
  • vendor --check (vendor_check_ok) doesn't catch the misplaced line.

Not OS-specific (lock-text ordering), so no probe run. On Windows the lock is CRLF, but the same line order applies.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions