Repository navigation
Hosted yarn classic refuses a patch that doesn't change dependencies when the registry metadata's ranges differ from the tarball's package.json (redirect_yarn_classic_dep_manifest_unlocked) #1578
Description
[agent] Found by the scheduled Yarn classic (1.x) bug-hunt routine (ledger #304).
Summary
The #591 check in hosted yarn classic compares every name@range in the served tarball's package.json against the yarn.lock keys, and refuses the pin when one isn't keyed. But yarn 1 keys a package's dependency blocks by the ranges in the registry metadata (packument). It writes the block's dependencies: sub-map from the tarball's package.json. When the two disagree, the check fires on a patch that doesn't touch package.json at all.
Real example: meteor-node-stubs@1.2.3 (also 1.2.2). Its packument lists 23 dependencies as "*", and its tarball package.json lists real ranges (url: ^0.11.0, …). On yarn 1.7.0, 1.10.1 and 1.22.22 the lock says:
meteor-node-stubs@1.2.3:
...
dependencies:
url "^0.11.0" # from the tarball
...
url@*: # key from the packument
The hosted scan then reports redirect_unconfirmed plus redirect_yarn_classic_dep_manifest_unlocked: "the patched meteor-node-stubs@1.2.3 depends on assert@^2.0.0, browserify-zlib@^0.2.0, … which yarn.lock does not lock … Lock them first (for example yarn add …)". The patch only changes package/deps/assert.js. The lock is left unpinned, and the scan exits 0 success. The suggested remedy doesn't help. The pin itself works fine:
- v4.0.0 (before the check) writes the pin.
yarn install --frozen-lockfileinstalls the patched bytes.- Main's own
vexattests that pin, both installed and lock-only.
Main's rescan of that v4 pin reports it redirect_unconfirmed ("an earlier run's pin of it is not confirmed"), so scan and vex disagree about the same lock.
A registry scan (packument vs tarball, about 1,500 packages) found more packages of this kind. Most are git dependencies that npm's publish-time normalisation rewrites in the packument (user/repo → github:user/repo, https://github.com/x/y → git+https://github.com/x/y.git): grunt-webfonts@1.1.3, gulp-webdriver@1.0.3, liquidjs-lib@5.1.6, xmlshim@0.2.7, @bitgo/utxo-lib@1.7.x, among others. They probably hit the same false refusal. That's not reproduced here, because the sandbox can't reach codeload.github.com.
Vendored mode is unaffected for a patch that doesn't list package/package.json: it wires the lock, and the fresh frozen install is patched.
Impact
Hosted mode can never patch these packages, and gives a misleading remedy. The run still exits 0 success, so CI pipelines report the package as handled when the warning is easy to miss.
Repro (Linux, yarn 1.22.22; same on 1.7.0 / 1.10.1)
mkdir p && cd p
echo '{"name":"r","version":"1.0.0","private":true,"dependencies":{"meteor-node-stubs":"1.2.3"}}' > package.json
yarn install
grep -n '^url@' yarn.lock # url@*:
awk '/^meteor-node-stubs@/,/^$/' yarn.lock | grep ' url ' # url "^0.11.0"
# Mock patch API serving a meteor-node-stubs@1.2.3 patch that only prepends a comment to package/deps/assert.js
# (the run-18 ledger mock with MN=meteor-node-stubs MV=1.2.3 MF=package/deps/assert.js)
socket-patch scan --mode hosted --json --yes
# events: redirect_unconfirmed pkg:npm/meteor-node-stubs@1.2.3
# warnings: redirect_yarn_classic_dep_manifest_unlocked "... depends on assert@^2.0.0, ..., url@^0.11.0, ... which yarn.lock does not lock"
# exit 0, yarn.lock unchangedReproduced twice per version.
Expected vs actual
- Expected: docs/ecosystems.md (hosted yarn classic) and CLI_CONTRACT
redirect_yarn_classic_dep_manifest_unlockedsay the refusal is for a patch that "adds a dependency (or changes a range) that noyarn.lockblock locks", because "a pin would install the patched package without that dependency". Here the patch changes no dependency, and yarn installs every one of them through the*blocks, so the entry should be pinned. That's what v4.0.0 did, and main'svexagrees the pin is good. - Actual: the pin is refused (or an existing pin is never confirmed) with a remedy that can't clear it.
Matrix (Linux; the other OSes don't matter, this is lock-text logic)
| yarn | hosted main e76cb9b |
hosted v5.0.1 | hosted v4.0.0 | vendored main |
|---|---|---|---|---|
| 1.7.0 | refused | – | – | – |
| 1.10.1 | refused | – | – | – |
| 1.22.22 | refused (×2) | refused | pins, frozen install patched | pass, frozen install patched |
First bad release: v5.0.1 (the #591 fix; v5.0.0 isn't on npm). v4.0.0 pins it correctly.
Suspect code
crates/socket-patch-core/src/patch/redirect/mod.rs:4144: unlocked_descriptors_in(locked, pkg) checks the tarball manifest's descriptors against the lock keys. A narrower check would refuse only descriptors where the tarball's manifest differs from the block's current sub-map (what the patch actually changes), since the existing sub-map entries are already locked under whatever pattern yarn resolved them by. The same assumption probably sits in vendored vendor_dep_manifest_unlocked for a patch that does list package/package.json (vendor/yarn_classic_lock.rs).
No probe branch was needed.
Activity
mikolalysenko commentedon Oct 15, 2026
[agent] Triage:
priority:p1(Yarn classic). Not a duplicate. Related to #1558 (also yarn classic dependency-map checking) but a different code path: this one is the hosted refusal inunlocked_descriptors_in(patch/redirect/mod.rs), which checks tarball-manifest ranges against lock keys; #1558 is the lock-only VEXdep_maps_declarecomparison. Not clustered. No open PR covers it yet.Generated by Claude Code
mikolalysenko commentedon Oct 16, 2026
[agent] Claiming this issue (with #1558; shared root cause:
formats/yarn/classic_deps.rsreads the patched package.json's dependency maps literally instead of the way yarn 1 records them in a lock block: it drops""ranges, skipscleanDependenciesnormalisation, and ignores the descriptors the block's sub-map already carries). Branch: agent/fix-yarn-classic-dep-manifest-model. Claim-ID: 2026-10-16T00:20:55Z-09f30cGenerated by Claude Code
mikolalysenko commentedon Oct 16, 2026
[agent] Draft PR: #1596
Generated by Claude Code
Read yarn classic patched manifests as yarn does
mikolalysenko commentedon Oct 16, 2026
[agent] Yarn classic bug-hunt run 43 re-check (Linux, local mock patch API, meteor-node-stubs@1.2.3, patch prepends a marker to
package/deps/assert.js).New shape on main
657fce2: vendored is hit too when the patch also listspackage/package.json. I tested a patch that changes only thedescription.scan --mode vendoredexits 1 withvendor_dep_manifest_unlocked, and the lock stays unchanged. Seen on yarn 1.10.1 and 1.22.22.PR #1596 (head
4d3698c1) fixes every cell I re-ran:657fce2redirect_yarn_classic_dep_manifest_unlocked), lock unchangedvexattestvendor_dep_manifest_unlockedvendor_dep_manifest_rewritten); frozen install patched; bothvexmodes attesturlto^0.12.0(unlocked)url@^0.12.0kind-ofOne side note, so nobody files it: a plain
yarn installre-keys meteor-node-stubs' dependency blocks (url@*→url@^0.11.0,buffer6.0.3 → 5.7.1). The unpatched lock does the same thing, so this is yarn 1's own packument-vs-tarball behaviour and not something socket-patch causes.Generated by Claude Code