Fix yarn berry pin entry rendering (#697, #718) - #719
Mikola Lysenko (mikolalysenko) wants to merge 7 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Hosted and vendored Yarn Berry pins copied the registry entry's body and spliced checksum: in wherever it fit. Yarn re-renders the entry from the tarball's own package.json in a fixed field order, so platform-conditional packages (checksum before conditions:, #697) and packages whose bin paths the registry normalizes (uuid, acorn, #718) got a lock that every yarn install --immutable rewrote (YN0028). Both writers now build the entry through one renderer that orders fields as yarn does and takes bin: from the tarball manifest: the vendored tarball's own package.json, or the served tarball's when the hosted caller supplies it. Assisted-by: Claude Code:claude-opus-5-5
0a06c9d to
4703893
Compare
The hosted yarn berry pin needs the served tarball's own package.json to write the bin: map yarn will expect (#718). Hosted scans now fetch that tarball, the way they already fetch wheel metadata, but only for berry lock entries that carry a bin: map, so projects without bins make no extra requests. Both the CLI scan and the in-memory engine do this. If the tarball can't be fetched or read, the patch is skipped as npm_manifest_unavailable instead of pinning an entry yarn would rewrite on the next install. Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
The served-manifest fetch matched any lock key that started with the package name, so a fork alias (left-pad@npm:other@...) with a bin: map could queue a download. If that download failed, the real patch was dropped. The fetch now targets only entries the berry pin would re-key: plain npm: descriptors of the package, or an earlier hosted pin of it. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent] Generated by Claude Code |
Hosted yarn berry scans got about 40% slower: the served-manifest targeting split and copied every lock entry once per patched package, even in projects with no bin: maps. The lock is now split once, keeping only entries that carry bin:, and each package is checked against just those (usually none). Locally the yarn-berry hosted and rescan benchmarks went from +37%/+45% back to within noise. Assisted-by: Claude Code:claude-opus-5-5
|
[agent] Generated by Claude Code |
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ 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 635ca29. Configure here.
|
[agent] Three CI failures on 635ca29 turned out to be infrastructure, not this PR. Each failed job was re-run once and passed, and all 491 checks on 635ca29 are now green.
Generated by Claude Code |
|
[burn-down agent] Ready for review at head
Slack announcement not sent: this run has no Slack send tool. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #697
Fixes #718
Summary
Yarn Berry pins written by
scan --mode hostedandvendor/scan --mode vendoredcould produce ayarn.lockthat yarn rewrites on the next install. CI then fails withyarn install --immutable(YN0028), and vendored projects hit it on every install. This PR makes both writers produce the entry exactly as yarn writes it.Shared root cause
Both writers built the pinned entry by copying the registry
npm:entry's body and insertingchecksum:wherever it fit:patch/redirect/mod.rs): kept every registry line and, if the entry had no checksum, inserted one directly afterresolution:;vendor/yarn_berry_lock.rs): wroteversion,resolution, the carried sections, thenchecksum,languageName,linkType.Yarn does something different when it re-resolves the pin:
checksum:out of yarn's field order on platform-conditional lock entries (conditions: os=…), so everyyarn install --immutablefails YN0028 #697). Yarn always writes the fields in one order:version,resolution,dependencies,peerDependencies,dependenciesMeta,peerDependenciesMeta, then every other field alphabetically (bin,checksum,conditions,languageName,linkType). Platform-conditional packages carry aconditions:line, so a checksum written after it (vendored) or before the dependency maps (hosted) gets moved.bin:comes from the tarball (Yarn berry vendored and hosted pins copy the registry entry'sbin: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). For a tarball-URL orfile:locator, yarn builds the entry from the tarball's ownpackage.json. That file keeps the published spelling (./dist/bin/uuid), but the registry metadata has a normalized one (dist/bin/uuid).Fix
formats::yarn::berry_entryholds one pure renderer,render_pinned_entry, that both writers now use. It orders fields the way yarn's lockfile serializer does and replacesbin:with the tarball manifest's map. That map is read the way yarn'sManifestreads it: scope dropped from keys, a stringbinnames the package itself, backslashes become/, and values are quoted the way yarn's YAML writer quotes them.binfrom thepackage.jsoninside the tarball it just packed and verified.package.json. It now downloads the served tarball, the same way it already downloads wheel metadata for uv, and checks it against the grant's sha512. It only does this for berry lock entries that have abin:map, so berry projects without bins make no extra requests. Both the CLI flow (scan/hosted.rs) and the in-memory engine (hosted::memory, which the Node binding uses) do this. If the tarball can't be fetched or read, the patch is skipped asnpm_manifest_unavailable(redacted detail) rather than pinning an entry yarn would rewrite.Verification with real yarn 4.12.0 (manual, this run)
file:and tarball-URL resolutions ofuuid@9.0.1writebin: uuid: ./dist/bin/uuid, while thenpm:entry writesdist/bin/uuid. This confirms the model behind Yarn berry vendored and hosted pins copy the registry entry'sbin: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.file:entry (@rollup/rollup-linux-x64-gnu),checksum:placed beforeconditions:passes--immutable, and placed after it fails YN0028. This confirms Yarn berry vendored and hosted pins putchecksum:out of yarn's field order on platform-conditional lock entries (conditions: os=…), so everyyarn install --immutablefails YN0028 #697.scanon auuid@^9project wrotebin: ./dist/bin/uuid. A fresh clone passedyarn install --immutable, plain and withYARN_ENABLE_HARDENED_MODE=1.scan --mode vendored(patcheddist/index.js) wrote the same. A fresh clone installed the patched bytes and passed plain and hardened--immutable.bin:back to the registry spelling fails YN0028 with the exact diff from Yarn berry vendored and hosted pins copy the registry entry'sbin: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.Tests (red → green)
Run on the pre-fix code, the four regression tests failed (
test result: FAILED. 0 passed; 4 failed). With the fix, they pass.patch::redirect::tests::issue_697_checksum_lands_in_yarn_field_ordervendor::yarn_berry_lock::tests::issue_697_conditional_entry_keeps_yarn_field_orderpatch::redirect::tests::issue_718_bin_comes_from_the_served_tarball_manifest, CLIscan::hosted_yarn_berry_manifest::issue_718_hosted_pin_takes_bin_from_the_served_tarball, in-memoryhosted_memory_engine::yarn_berry_pin_takes_bin_from_the_served_tarballvendor::yarn_berry_lock::tests::issue_718_bin_map_comes_from_the_tarball_manifestThe fetch targeting has its own test,
patch::redirect::tests::berry_pin_needs_manifest_only_for_entries_the_pin_rekeys, added after Bugbot found the fork-alias case. New unit tests also cover the renderer (formats::yarn::berry_entry::tests), the manifest decoder (hosted::npm_manifest::tests), and the fetch-failure skip (scan::hosted_yarn_berry_manifest::unfetchable_served_manifest_skips_the_patch).Local checks:
cargo clippy --workspace --all-features -- -D warnings: clean.cargo test -p socket-patch-core --all-features --lib: 4853 passed, 4 failed. The 4 (copy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,pypi_poetry::wire_write_failure_…,pypi_requirements::wire_failure_rolls_back_…) inject failures with read-only permissions, which root ignores. This sandbox runs as uid 0, and those files are untouched by this PR.cargo test -p socket-patch-cli --all-features --test scan --test hosted_memory_engine ...: all green forscan(106),hosted_memory_engine(29),in_process_vendor(104) and the real-yarn 4.12.0 suitese2e_redirect_yarn_berry_build(14),e2e_vendor_yarn_berry_build(14),e2e_yarn4_pnpm_linker_build(15),e2e_yarn4_workspaces_build(13). The sandbox-only failures: 3 write-failure-injection tests inin_process_redirect(root again), and 2mode_migration_npmberry tests whose harness fetches left-pad from registry.npmjs.org withreqwestand rejects the sandbox proxy CA (InvalidCertificate(UnknownIssuer)) before socket-patch runs. CI covers both.cargo fmt --all -- --checkis not clean onmainwith the pinned 1.93.1 toolchain (131 files), and CI doesn't run it, so I only formatted the code this PR adds. I didn't reformat whole files.cargo test --workspace --all-featuresbuild of every test binary didn't fit in this session's disk allowance, so I'm relying on CI for the complete run.npm/,pypi/,gem/) only dispatch to the binary, so they needed no changes. The Node binding gets the fix throughrun_in_memory.🤖 Generated with Claude Code
https://claude.ai/code/session_015eyeXdc6C6LN4UcGYpw3hR
Note
Medium Risk
Changes hosted redirect and lock rewrite behavior for Yarn Berry projects with
bin:entries; failed manifest fetches now skip patches instead of pinning, which is safer but changes outcomes for offline or broken artifact URLs.Overview
Fixes yarn Berry hosted and vendored lock pins so
yarn install --immutableno longer fails (YN0028).Shared renderer (#697, #718): Adds
formats::yarn::berry_entrywithrender_pinned_entry, used by both the hosted redirect and vendoredyarn_berry_lockwriters instead of copying registry lines and regex-splicingresolution/checksum. Entries now match yarn’s field order and YAML scalar quoting;bin:comes from the tarball’spackage.jsonviamanifest_bin, not the registry lock entry.Hosted scan (#718): For berry locks whose pin would re-key an entry that already has
bin:, the CLI and in-memory engine download the served npm tarball (sha512-checked), decodepackage.jsonthrough newhosted::npm_manifest, and pass manifests in the same URL-keyed metadata map as wheel METADATA. Unfetchable tarballs skip the patch asnpm_manifest_unavailable(redacted URL) instead of writing a pin yarn would rewrite.yarn_berry_manifest_targets/berry_pin_needs_manifestlimit fetches to re-keyable bin entries (not fork aliases).Docs:
docs/ecosystems.mddocuments the manifest fetch and skip reason for yarn berry hosted redirects.Reviewed by Cursor Bugbot for commit 635ca29. Configure here.
Generated by Claude Code