Repository navigation
Vendored yarn classic: after a git merge resolves .socket/vendor/state.json to one side, vendor --check and rollback exit 0 while the other side's package stays wired, and rollback deletes the ledger, stranding it #991
Description
[agent] Found by the scheduled Yarn classic (1.x) bug-hunt routine (ledger #304).
Summary
This is a common team flow. Two branches each run scan --mode vendored for a different package. Git merges yarn.lock cleanly (the blocks are different), but .socket/vendor/state.json conflicts (AA, both sides add an entry under entries). The obvious resolution is git checkout --ours or --theirs. It leaves the other branch's package wired in yarn.lock to its committed tarball, with no ledger entry. socket-patch handles that state inconsistently:
vendor --checkexits 0 ("committed artifact and wiring verified") and audits only the remaining ledger entry. It also exits 0 with no events once the ledger is gone entirely, while yarn.lock still resolves into.socket/vendor/.rollbackexits 0: "Reverted vendoring for pkg:npm/left-pad@1.3.0", with no event or warning for the unrecorded wiring. It then deletes state.json, so is-number stays wired and its tarball stays committed, with no ledger at all.- After that, a second
rollbackfails: "the vendor ledger is missing — restore .socket/vendor/state.json from version control". So doesscan --mode vendored("the lockfile references this patch without its vendor ledger entry; restore .socket/vendor/state.json from version control"). No commit in history has a ledger with the is-number entry next to the merged lock, so that remedy doesn't apply. The only exits are hand-editing state.json orgit checkout -- yarn.lock. remove pkg:npm/is-number@7.0.0exits 1not_found.vendor --revertexits 0 and skips it withvendor_orphan_still_wired.
repair already detects this exact shape: exit 1, vendor_ledger_missing, "a lockfile references .socket/vendor/npm// but the vendor ledger has no entry for it". So does scan. Only the CI gate (vendor --check) and rollback miss it.
Impact
vendor --checkis the documented CI gate, and it stays green on a project whose ledger no longer covers a wired package. The nextrollbackreports success but leaves that package patched and committed. It also destroys the ledger that the remaining entries' originals lived in, and every later rollback or re-vendor then fails with an unusable remedy.- Users read "rollback succeeded" as "nothing is vendored any more". yarn's frozen install keeps installing the patched is-number from the orphaned tarball.
Repro (yarn 1.22.22; local mock patch API serving left-pad@1.3.0 and is-number@7.0.0)
git init -q app && cd app
echo '{"name":"app","version":"1.0.0","private":true,"dependencies":{"left-pad":"1.3.0","is-number":"7.0.0"}}' > package.json
yarn install && echo node_modules > .gitignore && git add -A && git commit -qm base
git checkout -qb a && socket-patch scan --mode vendored --yes --package pkg:npm/left-pad@1.3.0 && git add -A && git commit -qm a
git checkout -q main && git checkout -qb b && socket-patch scan --mode vendored --yes --package pkg:npm/is-number@7.0.0 && git add -A && git commit -qm b
git checkout -q a && git merge --no-edit b # CONFLICT (add/add) in .socket/vendor/state.json; yarn.lock auto-merges
git checkout --ours .socket/vendor/state.json && git add -A && git commit -qm merge
socket-patch vendor --check; echo $? # 0 — "pkg:npm/left-pad@1.3.0: committed artifact and wiring verified"
socket-patch repair; echo $? # 1 — vendor_ledger_missing: "a lockfile references .socket/vendor/npm/2222…/ but the vendor ledger … has no entry"
socket-patch rollback; echo $? # 0 — "Reverted vendoring for pkg:npm/left-pad@1.3.0"
ls .socket/vendor/state.json # gone
grep -A2 '^is-number' yarn.lock # resolved "file:./.socket/vendor/npm/2222…/is-number-7.0.0.tgz#…"
socket-patch vendor --check; echo $? # 0 — no output at all
socket-patch rollback; echo $? # 1 — "vendor ledger is missing — restore .socket/vendor/state.json from version control"
rm -rf node_modules && yarn install --frozen-lockfile # installs the patched is-number from the orphaned tarballA manual JSON union of both sides' entries works end to end: vendor --check verifies both, and rollback is byte-exact to the base lock. So the bug is the handling of the one-sided resolution, not the merge itself.
Expected vs actual
- CLI_CONTRACT "Vendored JVM support (v5)", the
vendor --checkparagraph: "Missing ledger entries fail withvendor_ledger_missing." Actual: a lockfile reference with no ledger entry passesvendor --check(exit 0). For JVM,run_checkalready refuses "artifacts exist without a vendor ledger" (vendor.rs:983-993); npm has no equivalent. - CLI_CONTRACT rollback "State discovery": "A project whose lockfiles still reference
.socket/vendor/artifacts but whose vendor ledger is missing errors asking for.socket/vendor/state.jsonto be restored". Actual: with a partial ledger, rollback exits 0, skips the unrecorded reference silently, and deletes the ledger, which creates exactly the state the contract refuses. - Expected:
vendor --checkfails (vendor_ledger_missing, naming the referenced uuid/path, asrepairdoes).rollbackeither refuses before writing or warns and keeps the ledger. The remedy text should cover the merge case (merge both sides'entries).
OS × version
| OS | yarn | result |
|---|---|---|
| Linux | 1.7.0 | reproduces (×2) |
| Linux | 1.10.1 | reproduces (×2) |
| Linux | 1.22.22 | reproduces (×2) |
This is ledger and lock-scan logic with no OS-specific paths, so macOS and Windows weren't probed. It is likely npm-family-wide (the check loop is ecosystem-generic), but it was only measured with yarn classic.
First bad: not a regression. v4.0.0 has no vendor --check, and its rollback skips vendored entries ("use remove or vendor --revert"). Tested on main 9c43dfc.
Suspect code
crates/socket-patch-cli/src/commands/vendor.rs:974run_check: iterates onlystate.entriesand manifest keys, and never callsvendored_backend::repair::scan_vendor_references(whichrepairuses atvendored_backend/repair.rs:585to emitvendor_ledger_missingfor uncovered references).crates/socket-patch-cli/src/commands/rollback.rs:~1100: thescan_vendor_referencesguard runs only when the ledger is absent, not for references the present ledger doesn't cover.
Activity
mikolalysenko commentedon Oct 7, 2026
[agent] Triaged as
priority:p1(yarn classic, and probably npm-family-wide). It isn't a duplicate:--manifest-pathledger resolution.None of them covers a lockfile reference that the ledger doesn't record. On
main,run_check(crates/socket-patch-cli/src/commands/vendor.rs:974) only iteratesstateentries for npm-family. The "artifacts without a ledger" refusal exists only on the JVM path, which matches the report. No open PR addresses it.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
v5 triage: P2, not a release blocker. Retain reconciliation after a vendor ledger was resolved to one side of a git merge at P2. This is recovery from altered state, not a first-run release requirement.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commentedon Oct 14, 2026
[agent] Re-checked on main
a3f56f7(yarn 1.22.22, Linux), using the repro from the issue body with a two-package local mock. One of the two halves is fixed.vendor --check. It now exits 1 withvendor_ledger_missingnext tovendor_check_okfor left-pad.repairalso still exits 1 withvendor_ledger_missing.rollback. It exits 0success, with onerolledBackevent (left-pad) and only the genericreinstall_requiredwarning. It deletes.socket/vendor/state.jsonand the left-pad uuid dir. Meanwhileyarn.lockstill resolvesis-number@7.0.0tofile:./.socket/vendor/npm/2222…/is-number-7.0.0.tgz, and that tarball stays committed. Nothing in the envelope names the orphaned wiring, even thoughvendor --checkandrepairin the same checkout detect it.So the CI gate now catches this state, but a
rollbackrun before anyone fixes it still reports success and removes the ledger.Generated by Claude Code