Repository navigation
Vendored pnpm 7/8 writes the absolute file: specifier unquoted, so a project path containing # or : breaks every frozen install while vendor, vendor --check and vex report success #754
Description
[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
On pnpm 7 and 8 locks (lockfile 5.4 / 6.0), vendored mode rewrites the root dependency's specifier to the machine-absolute file:<project root>/.socket/vendor/...tgz spelling that pnpm itself records. It splices that value into pnpm-lock.yaml as a plain YAML scalar with no quoting. When the project path contains a YAML indicator sequence, the lock no longer says what socket-patch meant:
#(for example~/src/My Project #2/): YAML reads everything after#as a comment. pnpm sees the specifierfile:/…/My Projectand refuses withERR_PNPM_OUTDATED_LOCKFILE.:(for examplecolon: x): the line is no longer valid YAML. pnpm fails withERR_PNPM_BROKEN_LOCKFILE … bad indentation of a mapping entry.
pnpm writes the same value single-quoted when it generates the lock itself. In the same directory, pnpm install from the vendored package.json produces specifier: 'file:/…/hash #x/.socket/vendor/…/left-pad-1.3.0.tgz'.
Impact
scan --mode vendored/vendorreportsuccess, and the checkout is then uninstallable with--frozen-lockfile(the CI default) at the same path, not only in a moved checkout. That contradicts thevendor_pnpm_legacy_absolute_specifiercaveat, which promises that the lock works at the recorded path.vendor --checkreportsvendor_check_ok("committed artifact and wiring verified") on the broken lock.vexattestsnot_affectedfor a project that can't be installed from its lock.vendor --revertrestores the original correctly. Only the forward write is wrong.
Repro (Linux, pnpm 8.15.9; 7.33.7 is the same)
D="/tmp/w/hash #x"; mkdir -p "$D" && cd "$D"
printf '{"name":"proj","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}\n' > package.json
pnpm install
socket-patch scan --mode vendored --json --yes # status: success (any left-pad@1.3.0 patch; a mock API was used)
grep specifier pnpm-lock.yaml
# specifier: file:/tmp/w/hash #x/.socket/vendor/npm/<uuid>/left-pad-1.3.0.tgz <- unquoted
rm -rf node_modules
pnpm install --frozen-lockfile --offline
# ERR_PNPM_OUTDATED_LOCKFILE ... specifiers in the lockfile ({"left-pad":"file:/tmp/w/hash"}) don't match specs in package.json
# (the lock value is truncated at " #")
socket-patch vendor --check --json # status: success, vendor_check_ok
socket-patch vex --output v.json # not_affectedOn a 5.4 lock (pnpm 7.33.7), the line written is specifiers: → left-pad: file:/…/hash #x/.socket/vendor/…tgz, which fails the same way.
Expected vs actual
- Expected: the vendored lock round-trips through pnpm's own YAML reader to the exact specifier pnpm would record. docs/ecosystems.md and the
vendor_pnpm_legacy_absolute_specifierwarning say that a frozen install works in a checkout at the recorded path. CLI_CONTRACT: a reported success must leave a project that installs patched. - Actual: the value is written unquoted, so YAML truncates or rejects it.
vendor --checkandvexdon't notice.
Matrix (Linux, main 045d7ec; each cell = vendored scan, then a fresh --frozen-lockfile --offline install)
| project dir name | pnpm 7.33.7 (5.4) | pnpm 8.15.9 (6.0) |
|---|---|---|
plain dir |
pass | pass |
ünïcode |
pass | pass |
a'quote, [br], x#y |
pass | pass |
hash #x |
fail ERR_PNPM_OUTDATED_LOCKFILE |
fail ERR_PNPM_OUTDATED_LOCKFILE |
colon: x |
fail ERR_PNPM_BROKEN_LOCKFILE |
fail ERR_PNPM_BROKEN_LOCKFILE |
Reproduced twice on each failing cell. pnpm ≥ 9 vendored locks use only relative specifiers, so they aren't affected. Windows paths (C:/Users/x/My Project #2) would hit the same splice, but I haven't tested that on a runner.
First bad release: release 4.0.0 (npm) writes the same unquoted line and fails identically. 3.3.0 has no vendored mode. So it isn't a regression.
Suspect code
crates/socket-patch-core/src/vendor/pnpm_lock_legacy.rs:593(v5.4specifiers:line):format!(" {}: {}", yaml_key_like(key, repr), ctx.abs_spec)crates/socket-patch-core/src/vendor/pnpm_lock_legacy.rs:654(v6.0 nestedspecifier:):format!(" specifier: {}", ctx.abs_spec)abs_specis built atpnpm_lock_legacy.rs:358from the canonical root without YAML escaping. The in-sync comparisons at:578/:630would also need to compare the decoded value.vendor --checkevidently validates the same raw string, so it agrees with the broken write.
Probe runs: none (Linux reproduction only).
Activity
mikolalysenko commentedon Oct 4, 2026
[agent] Triaged:
priority:p1(pnpm). Confirmed on main045d7ec:crates/socket-patch-core/src/vendor/pnpm_lock_legacy.rsbuildsabs_specat :358 and splices it unquoted at :593 (5.4specifiers:) and :654 (6.0specifier:); the in-sync checks at :578/:630 compare the raw string. No duplicate or existing fix found. Not clustered: no other open issue shares this YAML-scalar emission path.Generated by Claude Code
mikolalysenko commentedon Oct 4, 2026
[agent] Claiming this issue (shared root cause: pnpm 5.4/6.0 vendored writer splices the absolute file: specifier as a plain YAML scalar). Branch: agent/fix-pnpm-legacy-specifier-yaml-quote. Claim-ID: 2026-10-04T06:20:32Z-dce77c
Generated by Claude Code
mikolalysenko commentedon Oct 4, 2026
[agent] Draft PR: #755
Generated by Claude Code
pnpm: run 16 ledger (filed #754, #756; #636 comment)
Quote pnpm 7/8 vendored specifier when needed
Run real pnpm 7/8 vendoring under # and : paths
Skip the ": " test dir on Windows
Drop unrelated cargo fmt --all churn from the #754 fix
Fix pnpm 7/8 vendored specifier YAML quoting (#754) (#755)