Repository navigation
Vendored pnpm 12 with packageManager set: the two-document pnpm-lock.yaml makes vendor refuse, and vendor --revert, rollback and the hosted takeover half-revert the project and break frozen installs #466
Description
[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
When package.json has a packageManager: "pnpm@12.x" field, pnpm 12 writes pnpm-lock.yaml as two YAML documents. The first is an "env" document with its own importers: (configDependencies, packageManagerDependencies), packages: (the pnpm / @pnpm/exe.* entries) and snapshots:. The second is the project lock. pnpm 11.28.3 and 10.34.5 don't do this with the same package.json.
The vendored line-block planner finds sections with section_bounds, which returns the first column-0 packages: / importers: / snapshots: header. So on this lock it reads and edits the env document, except for overrides:, which exists only in the second document. As a result:
scan --mode vendored/vendorrefuses wrongly. It reportsvendor_lock_entry_not_found("pnpm-lock.yaml has no packages entry for left-pad@1.3.0 — make sure the package is installed and locked"), but the package is installed and locked. This fails closed (exit 1), but the refusal shouldn't fire.vendor --reverton a project vendored before the lock became two-doc: exit 0,status: success. It removespnpm.overridesfrom package.json, deletes the scaffolded pnpm-workspace.yaml and removes the lock'soverrides:block. It leaves all 5file:.socket/vendor/...references in the importers, packages and snapshots of document 2. Every event isskipped/vendor_lock_entry_drifted("packages entry ... no longer exists"), which is false. The nextpnpm install --frozen-lockfilefails withERR_PNPM_OUTDATED_LOCKFILE.rollbackdoes the same edits. It then reportsvendoredKept: [{reason: "lockfile wiring drifted; vendored state left untouched"}]and exits 1. The state was not left untouched: package.json, pnpm-workspace.yaml and the lock were all modified, and frozen installs break.- Plain
scan(v5 default hosted mode) does a vendored→hosted takeover. It warnsredirect_takeover_reverted_vendored, deletes the vendor ledger (.socket/vendor/state.json), package.json overrides, the workspace file and the lockoverrides:block, then warnsredirect_pnpm_entry_vendoredwithredirected: 0. It exits 0 withstatus: success. The project is left with no ledger, no hosted pin and a lock that--frozen-lockfilerejects (ERR_PNPM_OUTDATED_LOCKFILE). Because the ledger is gone, a laterrollbackcan only say "lockfiles still reference .socket/vendor/ artifacts but the vendor ledger is missing".
Hosted mode itself is fine on the two-doc lock. scan --mode hosted pins the entry in document 2, a fresh dead-registry frozen install lands the patched bytes, vex attests not_affected, and rollback restores the lock byte for byte.
Impact
packageManager is the standard way to pin pnpm (corepack), so any vendored pnpm 12 project with a pinned pnpm hits this. Adding packageManager to an already vendored project (or upgrading to pnpm 12 with it set) turns every unwind path into a silent, partial revert that breaks CI's frozen install. Two of those paths (vendor --revert and the default scan) exit 0 with success.
Repro
Needs pnpm 12.8.1, a socket-patch built from main, and a patch API serving a free patch for left-pad@1.3.0. A local mock of /v0/orgs/<org>/patches/{batch,package,view,by-package} and the hosted tarball was used, with SP="socket-patch … --api-url <mock> --org test-org --api-token fake".
set -u; W=$(mktemp -d); cd $W
echo '{"name":"app","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > package.json
$PNPM install --store-dir $W/.store >/dev/null
$SP scan --mode vendored --json --yes --cwd . >/dev/null 2>&1 && echo "1. vendored on single-doc lock: ok"
node -e 'const f="package.json",p=require("./"+f);p.packageManager="pnpm@12.8.1";require("fs").writeFileSync(f,JSON.stringify(p,null,2))'
$PNPM install --store-dir $W/.store >/dev/null
echo "2. lock documents: $(grep -c '^---$' pnpm-lock.yaml)"
cp -r $W $W.copy
$SP vendor --revert --json --yes --cwd . > revert.json 2>/dev/null; echo "3. vendor --revert exit=$? status=$(node -p 'require("./revert.json").status')"
echo " package.json overrides left: $(grep -c overrides package.json); pnpm-workspace.yaml: $(test -f pnpm-workspace.yaml && echo kept || echo deleted); lock file:.socket refs left: $(grep -c 'file:.socket' pnpm-lock.yaml)"
rm -rf node_modules; $PNPM install --frozen-lockfile --store-dir $W/.store2 2>&1 | grep -o 'ERR_PNPM_[A-Z_]*' | head -1
cd $W.copy; $SP scan --json --cwd . > s.json 2>/dev/null; echo "4. plain scan (takeover) exit=$? status=$(node -p 'require("./s.json").status') redirected=$(node -p 'require("./s.json").redirect.redirected')"
rm -rf node_modules; $PNPM install --frozen-lockfile --store-dir $W/.store3 2>&1 | grep -o 'ERR_PNPM_[A-Z_]*' | head -1Output (identical on two runs):
1. vendored on single-doc lock: ok
2. lock documents: 2
3. vendor --revert exit=0 status=success
package.json overrides left: 0; pnpm-workspace.yaml: deleted; lock file:.socket refs left: 5
ERR_PNPM_OUTDATED_LOCKFILE
4. plain scan (takeover) exit=0 status=success redirected=0
ERR_PNPM_OUTDATED_LOCKFILE
For symptom 1, vendor straight onto the two-doc lock: create the project with packageManager already set, run pnpm install, then scan --mode vendored. It exits 1 with download.patches[0].errorCode = "vendor_lock_entry_not_found".
Expected vs actual
- Expected: the vendored planners address the document that holds the project lock (the one with the root importer's
dependenciesand thesettings:header). That would make vendor, revert, rollback and takeover behave exactly as they do on the single-doc lock. A single-doc control on the same pnpm 12.8.1 passes: vendor, then rollback, gives a byte-exact lock, and the takeover givesredirected: 1with a fresh frozen install landing the patched bytes. Failing that, the planners should refuse up front (fail closed) rather than half-revert. - CLI_CONTRACT.md (rollback JSON,
vendoredKept): "Drift-keeps — wiring drifted, vendored state (and the manifest entry) left untouched". Here the state is edited even though it's reported as kept, andvendor --revertreportssuccesson a revert that left the lock wired to the artifact. formats/pnpm/mod.rs:290documents the assumption: "pnpm 9-12 emitlockfileVersion: '9.0'(single doc, first line)".
Matrix (Linux, Node 22, main 2463257)
| pnpm | packageManager set |
lock docs | vendor | vendor --revert / rollback | default-scan takeover | hosted |
|---|---|---|---|---|---|---|
| 12.8.1 | yes | 2 | fail (vendor_lock_entry_not_found) |
fail (half-revert, frozen install broken) | fail (exit 0, ledger lost, frozen install broken) | pass |
| 12.8.1 | no | 1 | pass | pass | pass | pass |
| 11.28.3 | yes | 1 | pass | not run | not run | not run |
| 10.34.5 | yes | 1 | not run (single doc) |
macOS and Windows weren't probed; the logic is OS-independent line splicing. First bad release: not bisected. Release 4.0.0 can't run against the v5-shaped mock, and v5 (#277) introduced the default-hosted takeover.
Suspect code
crates/socket-patch-core/src/formats/pnpm/lines.rs:14section_boundstakes the firstname:header in the file and ignores---document separators.crates/socket-patch-core/src/vendor/pnpm_lock.rs:509(preflight_package→lock_has_target_package_in, line 1285) and the revert path that emitsvendor_lock_entry_drifted. Every section lookup runs against document 1, but theoverrides:lookup succeeds in document 2.crates/socket-patch-core/src/formats/pnpm/mod.rs:290: the single-document assumption.
Side note (not filed separately): pnpm 12 prints The "pnpm" field in package.json is no longer read by pnpm … "pnpm.overrides" on every install of a vendored project. The workspace-file override is what takes effect, so this is noise only.
Activity
pnpm: run 5 ledger (#466)
mikolalysenko commentedon Oct 1, 2026
[agent] Triaged:
priority:p1(pnpm). Not a duplicate and no open PR addresses it. The core defect is the vendored pnpm lock planner'ssection_boundstaking the first column-0 section header, so it reads the pnpm 12 env document instead of the project document. Item 4 (vendored → hosted takeover reverting before the hosted rewriter skips) is a downstream symptom that also matches the takeover-ordering cluster #468 / #369; fixing that cluster would make item 4 fail closed, but the two-document lock handling needs its own fix here.Generated by Claude Code
mikolalysenko commentedon Oct 1, 2026
[agent] New information from the pnpm bug-hunt routine (ledger #303), on main
61cfb9b: the two-document lock isn't limited to pnpm 12 withpackageManager, and when the target also appears in the env document, vendor doesn't refuse. It reports success after rewriting the wrong document.Another trigger:
configDependencies, on pnpm 11 too.pnpm add --config <pkg>makes pnpm 11.28.3 and 12.8.1 writepnpm-lock.yamlas two documents. Document 1 holdsimporters: .: configDependencies:plus that package'spackages:/snapshots:entries. pnpm 10.34.5 keeps a single document (its config-dep integrity lives inpnpm-workspace.yaml), and vendoring works there.A worse symptom when the target is also a config dependency. If the vendored package is in both documents (for example
is-number@7.0.0as a regular dependency and as a config dependency),section_boundsfinds it in document 1. Vendor then exits 0 withstatus: success, but:file:.socket/vendor/...references (theoverrides:block, the importer specifier/version, and thepackages:/snapshots:keys) land in document 1 (the config-dependency env document). Document 2, the project lock, has 0.pnpm install --frozen-lockfile --offlinethen fails. On pnpm 11.28.3 that'sCannot update configDependencies with "frozen-lockfile" because the lockfile is not up to date, and on 12.8.1 it'sERR_PNPM_FROZEN_LOCKFILE_WITH_OUTDATED_LOCKFILE. The pristine lock installs frozen fine (control).pnpm installre-locks. The regular copy ends up patched (via thepackage.json/ workspace overrides), but the config-dependency copy innode_modules/.pnpm-configis not.vendor --revertrestores the lock, the workspace file and package.json byte for byte (pass).If the target is only in document 2 (for example
is-odd@3.0.1, withis-numberas a config dependency), you get the samevendor_lock_entry_not_foundrefusal this issue already describes.configDependenciesvendor_lock_entry_not_foundconfigDependenciesvendor_lock_entry_not_foundA fix should choose the project document explicitly. It isn't enough to skip a document that doesn't contain the key, because a config dependency can share the key.
Hosted mode on the same lock (pass, for contrast): it pins both documents'
is-number@7.0.0resolutions, a fresh frozen install lands the patched bytes in bothnode_modules/is-numberandnode_modules/.pnpm-config/is-number(on 11 and 12), androllbackrestores the lock byte for byte.Generated by Claude Code
mikolalysenko commentedon Oct 7, 2026
[agent] Claiming this issue as part of the pnpm open-issue sweep in draft PR #1007. Branch: agent/fix-pnpm-open-issues. Claim-ID: 2026-10-07T12:44:29Z-pnpm07
Fix vendored pnpm editing the wrong document of a two-document lock (#…
mikolalysenko commentedon Oct 7, 2026
[agent] PR #1007 fixes this issue. It has a regression test that fails on
main, CI is fully green (552 checks) and it's ready for review. The issue will close when #1007 merges. The PR description's table gives the root cause, the fix and the test for each issue.mikolalysenko commentedon Oct 8, 2026
[agent] Re-triage from the pnpm bug-hunt routine (ledger #303), main
cf8b164, pnpm 12.10.1, Linux. I used the repro from the issue body.Symptom 4 (default-scan takeover) is fixed by #1039 (the atomic vendored-to-hosted takeover). The two-document project now stays vendored and nothing is touched:
scan --dry-runand the wetscanboth exit 0 withredirected: 0,action: skipped,errorCode: vendored_revert_failedand the warningredirect_vendored_revert_failed.git statusis clean afterwards, the ledger is kept, and a freshpnpm install --frozen-lockfilestill lands the vendored patched bytes.Symptoms 1–3 still reproduce:
scan --mode vendoredstraight onto the two-document lock exits 1.vendor --revertexits 0 withsuccess. Its events arevendor_lock_entry_removed×3,vendor_artifact_keptandvendor_revert_kept. It still modifiespackage.jsonandpnpm-lock.yamland deletespnpm-workspace.yaml, and it leaves 5file:.socketreferences in the lock.rollbackexits 1 withpartial_failureandrolledBack: 0, after making the same three edits.The cleanest next step may be to give
vendor --revertandrollbackthe same staged savepoint that #1039 gave the takeover.Generated by Claude Code
Merge origin/main (#1008) into agent/fix-pnpm-open-issues