Skip to content

feat(cli): pin the pnpm store directory for the lockfile prune [RED-931] [ship] - #1474

Merged
sorccu merged 5 commits into
mainfrom
simo/red-931-pin-pnpm-store-dir-for-lockfile-prune
Sep 4, 2026
Merged

feat(cli): pin the pnpm store directory for the lockfile prune [RED-931] [ship]#1474
sorccu merged 5 commits into
mainfrom
simo/red-931-pin-pnpm-store-dir-for-lockfile-prune

Conversation

@sorccu

@sorccu sorccu commented Sep 4, 2026

Copy link
Copy Markdown
Member

Linear: RED-931

Problem

The lockfile pruner runs pnpm install --lockfile-only in a directory under the OS temp dir. pnpm picks its content-addressable store per project directory: the home-directory store when the directory can hard-link into it, otherwise an empty store at the root of the directory's mount. When the temp dir sits on a different filesystem than the home directory (tmpfs /tmp as in WSL2, TMPDIR on another volume, containers), the prune re-resolves against an empty store. Every package then needs registry metadata: without --prefer-offline the prune makes thousands of requests and times out; with it (#1471), a stale metadata cache can resolve a different version, which the subset check rejects, so the prune falls back either way.

Changes

  • Pin the store on the prune. A new PackageManager.storeDirCommand() (pnpm store path for pnpm, undefined for npm, yarn and bun, whose caches do not depend on the project location) runs in the workspace root, where it reads the workspace's own config and applies pnpm's same-mount rule to the real project dir. The pruner validates the printed versioned path (<storeDir>/v10) and passes its parent to the install as --config.storeDir=. The parent, not the path: pnpm appends its own version segment unless the path already ends in it, and the lookup and the install can resolve to different pnpm majors (corepack, mise, volta pick per directory), so pnpm 11 handed a …/v10 path would use …/v10/v11, an empty store again. Verified on pnpm 10.33.4 and 11.22.0; the setting is not recorded in the lockfile's settings section.
  • Fail closed. A lookup that fails or prints anything but an absolute versioned path is a notable skip ("the pnpm store directory could not be determined …"), not a run against an empty store. A missing pnpm is reported as such on every platform (the Windows PATH classification is now shared between probes and the install).
  • Shared time budget. Probe time is charged to the prune budget through a PruneBudget that also replaces the inline budget logic of the yarn version probe, so the documented timeout still bounds the whole prune.
  • Name the offending entries when the subset check fails: the warning lists up to eight unexpected resolutions by a short per-format name (peer suffixes stripped so a pnpm package counts once across packages and snapshots), each redacted, with the full list at debug level. Previously the warning only said that entries were unexpected.

Tests

  • Detector unit tests for the new command and the flag.
  • Stub-driven pruner tests for the lookup: parent-of-versioned-path pinning, last-line parsing, unusable output, failure, missing executable, vanished workspace root, timeout, budget exhaustion, unlimited budget, redaction of the debug log, and that failure reasons name the pinned command.
  • A real-pnpm decoy pair: a storeDir planted in the workspace's pnpm-workspace.yaml versus one materialized into the prune dir; the pinned run creates the former and not the latter, and the unpinned control the reverse. Passes with pnpm 10 and pnpm 11 on PATH.
  • Subset-check tests assert the named entries for pnpm, npm, bun and yarn, plus dedupe, truncation and redaction.

🤖 Generated with Claude Code

sorccu and others added 5 commits September 4, 2026 21:19
pnpm chooses its content-addressable store per project directory: the
home-directory store when the directory can hard-link into it, otherwise
an empty store at the root of the directory's mount. The lockfile pruner
runs its lockfile-only install in an OS temp dir, so on a temp dir that
sits on another filesystem than the home directory (tmpfs /tmp, a TMPDIR
on another volume, containers) the re-resolution ran against an empty
store: thousands of registry requests that time out, or, served by
--prefer-offline from a stale metadata cache, versions the subset
verification rejects.

The pruner now runs `pnpm store path` in the workspace root (a new
PackageManager.storeDirCommand), validates the printed versioned path and
pins its parent on the install via --config.storeDir. The parent, not the
path: pnpm appends its own version segment unless the path already ends
in it, and the lookup and the install can resolve to different pnpm
majors through corepack, mise or volta. An unusable answer is a notable
skip. Probe time is charged to the prune budget through a PruneBudget
shared with the yarn version probe, and the Windows missing-executable
classification is shared between probes and the install.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the subset check [RED-931]

When the regenerated lockfile resolves entries the original does not
have, the warning used to say only that; the entries themselves were
nowhere to be seen, which is what made an empty-store prune (a temp dir
on another filesystem, see the previous commit) hard to diagnose. The
reason now lists the unexpected entries by a short per-format name
(pnpm `name@version` with peer suffixes stripped so the packages and
snapshots records of one package count once, npm path plus version, bun
the tuple's name, yarn the entry's resolution), each redacted on its own
since tarball and git entries are URLs, capped with the usual `and N
more`; the complete list goes to the debug channel.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rence [RED-931]

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tion holds on Windows [RED-931]

The pruner renders the failing command through shellQuote, which wraps an
argument containing a backslash in quotes; a Windows temp path in the
stub's trailing store argument therefore no longer matched the expected
bare form.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…CRLF checkouts [RED-931]

The stub install script anchored its injection on LF newlines, which
never match the fixture on a Windows checkout with CRLF; the injection
silently no-oped and the prune succeeded. The shared rewrite helper with
newline-free anchors fails loudly when an anchor is absent.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@sorccu sorccu changed the title feat(cli): pin the pnpm store directory for the lockfile prune [RED-931] feat(cli): pin the pnpm store directory for the lockfile prune [RED-931] [ship] Sep 4, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-approved: ship/show PR from a same-repo branch.

@sorccu
sorccu merged commit 2ea1376 into main Sep 4, 2026
28 of 30 checks passed
@sorccu
sorccu deleted the simo/red-931-pin-pnpm-store-dir-for-lockfile-prune branch September 4, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant