pnpm 12 Is a Rust Rewrite That Keeps Everything, Except the Way It Resolves Your Private Git Dependency
pnpm 12 shipped stable on August 26. It is a rewrite of pnpm in Rust, and Zoltan Kochan is unusually direct in the release notes that it is "deliberately not a migration": the commands, flags, settings, and lockfile format of pnpm 11 all carry over.
That is a rare thing to be able to say about a rewrite, and mostly it holds. But there are five breaking changes, and one of them will break your build at an inconvenient hour if you depend on a private repository. That is the one worth reading carefully.
You cannot install it the way you think
Before anything else: latest on npm still points at the pnpm 11 line. pnpm 12 installs from the next-12 tag.
pnpm self-update next-12
Homebrew, winget, Scoop, and Chocolatey do not carry it yet. If you install pnpm through a package manager, you are on 11 for now whether you meant to be or not, which is a reasonable place to sit for a few weeks.
The change that will actually break you
Git dependencies are now identities rather than transports.
For repositories on GitHub, GitLab, and Bitbucket, a specifier names a repository and no longer chooses how to reach it. All four of these now resolve through the host's canonical HTTPS URL:
github:owner/repo owner/repo git+https://github.com/owner/repo git+ssh://git@github.com/owner/repo
The lockfile never records an SSH URL for those hosts anymore. So if you have a private dependency that has always worked because your manifest said git+ssh:// and your SSH agent had the key, pnpm 12 will try HTTPS and you will get an authentication failure on a package that installed fine yesterday.
The fix is real and it is one line, but it lives on the machine rather than in the repo:
git config --global url."git@github.com:".insteadOf https://github.com/
pnpm shells out to git, so the rewrite applies to every git operation it performs. Unknown hosts keep their exact URL, SSH included. A URL with embedded credentials is kept verbatim and never resolves to a host archive.
I want to flag why this is more annoying than it sounds. The fix is machine configuration, which means it does not travel with the repository. Your laptop gets fixed once and then you forget about it. Your CI runner does not have it. Every new contributor does not have it. This is the kind of change that works on your machine for three weeks and then fails on a fresh clone, which is the worst possible failure shape.
If you install private git dependencies in CI, go and check that before you upgrade, not after.
The silent failure that is now loud
An unrecognized setting in pnpm-workspace.yaml used to be ignored without comment. A misspelled minimumReleaseAge dropped the policy it was supposed to set, and nothing told you.
That is a security-relevant typo. minimumReleaseAge is the setting that stops you from installing a package version published nine minutes ago, which is the entire window most registry compromises operate in. Silently dropping it because of a capital letter is a genuinely bad default and I am glad it is gone.
pnpm 12 now fails the command with ERR_PNPM_UNRECOGNIZED_WORKSPACE_SETTINGS when the project pins a pnpm version that the running pnpm satisfies. The reasoning is sound: if the pin is honoured, the setting cannot have been meant for a different version, so it is a mistake rather than forward compatibility. Everywhere else it is a warning, so a messy project keeps working. pnpm config never fails on it, so you can still inspect and repair a broken file.
Lockfiles that stop churning
Dependency cycles are now broken canonically during peer resolution. Cycle members are ordered by package id, and the edges that close a cycle are always cut in the same place regardless of where the install walked in.
The consequence is the one you want: the lockfile becomes a pure function of the dependency graph. Reordered importers, reordered dependencies, and repeated installs now produce byte-identical lockfiles, which they could not before. On large cycle-heavy workspaces, peer resolution is 2 to 3 times faster, uses about 25% less memory, and produces a substantially smaller lockfile.
Existing lockfiles keep working. --frozen-lockfile consumes them unchanged, and an install that skips resolution leaves them alone. The first install that genuinely re-resolves re-keys the walk-order-dependent peer variants once, so expect one noisy diff and then quiet.
Two smaller things worth knowing
engineStrict follows the edge, not the subtree. An install now fails when an incompatible package is reached through a regular dependencies edge of an installable package, even if that whole subtree hangs off an optionalDependencies entry. pnpm 11 installed it and emitted a warning. Packages reachable only through optional edges are still skipped in both versions.
packageImportMethod: auto hardlinks first on Linux. A reflink materializes a new inode and copies extent bookkeeping inside the filesystem's metadata trees; a hardlink is one directory entry. On btrfs that roughly halves the time an install spends materializing node_modules from a warm store. ext4 is unchanged because cloning was never supported there. macOS keeps clone-first, because APFS clonefile is the cheap primitive on that platform.
The detail that tells you who is writing this
Buried in the fixes is this: when no directory above the project accepts a hard link, the default store is now created at <project>/node_modules/.pnpm-store instead of in the pnpm home directory.
The release notes name the case explicitly. An AI agent sandbox that grants write access only to the project, or a container with just the project mounted writable. In those environments the home store is read-only or on another volume, which forces every package to be copied rather than hard linked.
I have hit exactly this, repeatedly, in agent sandboxes, and spent an embarrassing amount of time assuming the install was slow because the container was small. It was not. It was copying every file because it could not link upward. Seeing it called out by name in a release note is a small thing but it tells you the maintainers are running the same setups the rest of us are.
Registry revisions, which are more interesting than they sound
A registry can now serve a replacement artifact for an already-published version. A rebuild with a vulnerability patched out, without changing the version number, and without rewriting the bytes that the canonical name@version URL has always served.
pnpm calls each of these a revision, addresses it by its complete SHA-512 digest, and records it as one extra line in the lockfile:
packages:
lodash@4.17.21:
resolution:
integrity: sha512-<replacement-digest>
revision: 1
An entry with no revision is revision 0, the original, which is what every entry pnpm has ever written means. So a lockfile that has adopted no replacements is byte-identical to today's. A dependency or override can pin a revision explicitly as <version>+rN, and pnpm update --patches refreshes locked artifacts without touching a single version number.
This only works if the registry serves them. pnpr does for the packages it hosts, and proxies them for an upstream registry that advertises them. Whether the public npm registry ever does is not pnpm's decision, which is the obvious limitation.
What I would actually do
Not upgrade this week, unless you have a specific reason.
Not because pnpm 12 looks bad. It looks careful, and the lockfile determinism alone is worth having. But latest still points at 11, the OS package managers do not carry 12 yet, and the git identity change has a failure mode that hides on your own machine and surfaces in CI. There is no prize for being early on a package manager.
What I would do now, in about ten minutes:
Run a search for git+ssh:// across your manifests. If you get hits on GitHub, GitLab, or Bitbucket URLs, write the insteadOf line into your CI setup step and your onboarding docs today, while it costs nothing. It is harmless under pnpm 11 and it is the difference between a smooth upgrade and a confusing one.
Then check pnpm-workspace.yaml for typos, particularly in minimumReleaseAge. Under pnpm 11 a typo there is silently doing nothing, and finding that out now is better than finding it out from the release that makes it an error.
Where I might be wrong
The strongest counterargument is that I am overweighting one breaking change that affects a minority of projects. Most solo repos have no private git dependencies at all, in which case pnpm 12 is a free speed and determinism upgrade and my caution costs you real improvements for no reason.
There is also a decent case for upgrading early precisely because it is a rewrite. Rust rewrites of mature tools find their remaining bugs through use, and the people who upgrade in week one are the reason week ten is stable. If your project is a side project with no deadline, being one of those people is a genuinely useful thing to do and I am not going to pretend otherwise.
Author
Lukas
@lukcombinator