Reproduction steps
- Build a container image that pre-populates the store at its default home location (
/home/<user>/.local/share/pnpm/store), so a later install has nothing to download.
- Run a container from that image with the workspace bind-mounted, so the workspace is its own mount rather than a directory under
$HOME.
- Install with a complete lockfile and watch the progress line:
Progress: resolved 2074, reused 0, downloaded 2074, added 2074, done
The pre-populated store is not used, and pnpm says nothing about why. reused 0 is the only hint, and it reads like a store that failed to populate rather than a store that was never consulted.
Describe the Bug
The relocation itself is deliberate and documented: resolve_store_dir probes whether the project can be hardlinked from the pnpm home directory, and when it cannot, re-resolves the store against the project's own volume:
if Sys::can_link_between_dirs(&pkg_root, pnpm_home_dir) {
return home_default;
}
…
if mountpoint == pkg_root {
return pkg_root.join("node_modules").join(".pnpm-store");
}
mountpoint.join(".pnpm-store")
A bind-mounted workspace is its own mount point, so mountpoint == pkg_root and the store becomes <workspace>/node_modules/.pnpm-store — empty, on every fresh container. The image's store is still sitting there, fully populated, and is never read.
The behaviour is right; the silence is the bug. Nothing is logged when the store moves away from the configured or default location, so the symptom a user sees is "my pre-populated image does nothing" with no thread to pull. It cost an afternoon to find here, and the same shape hits any CI that bakes a warm store into an image and mounts the workspace from elsewhere.
The TypeScript CLI has the same relocation in storePathRelativeToHome (pnpm11/store/path/src/index.ts) and is equally quiet about it.
Expected Behavior
When the store is resolved to somewhere other than the configured or default location, pnpm says so once, at debug level or above:
The store was moved to <workspace>/node_modules/.pnpm-store because <workspace> cannot be hardlinked from /home/user/.local/share/pnpm.
Set storeDir to keep it elsewhere.
pnpm doctor reporting the effective store path alongside the reason would cover the same ground for someone already suspicious.
Neither changes what an install does — the relocation stays, and a user who wants the home store can pin storeDir explicitly, which already skips this path entirely.
Reproduction steps
/home/<user>/.local/share/pnpm/store), so a later install has nothing to download.$HOME.The pre-populated store is not used, and pnpm says nothing about why.
reused 0is the only hint, and it reads like a store that failed to populate rather than a store that was never consulted.Describe the Bug
The relocation itself is deliberate and documented:
resolve_store_dirprobes whether the project can be hardlinked from the pnpm home directory, and when it cannot, re-resolves the store against the project's own volume:A bind-mounted workspace is its own mount point, so
mountpoint == pkg_rootand the store becomes<workspace>/node_modules/.pnpm-store— empty, on every fresh container. The image's store is still sitting there, fully populated, and is never read.The behaviour is right; the silence is the bug. Nothing is logged when the store moves away from the configured or default location, so the symptom a user sees is "my pre-populated image does nothing" with no thread to pull. It cost an afternoon to find here, and the same shape hits any CI that bakes a warm store into an image and mounts the workspace from elsewhere.
The TypeScript CLI has the same relocation in
storePathRelativeToHome(pnpm11/store/path/src/index.ts) and is equally quiet about it.Expected Behavior
When the store is resolved to somewhere other than the configured or default location, pnpm says so once, at debug level or above:
pnpm doctorreporting the effective store path alongside the reason would cover the same ground for someone already suspicious.Neither changes what an install does — the relocation stays, and a user who wants the home store can pin
storeDirexplicitly, which already skips this path entirely.