NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #3866 most downloaded on npm
Fast, disk space efficient package manager
Last release today
03 Oct 2026
Ships on a steady schedule
a new release about every 8 days
Most releases are documented
notes for 46 of the last 60 stable releases
25 versions withdrawn
withdrawn after publishing
5 years old
597 releases · first in 2021
One column per quarter.
This release moves the WebContainer build into a separate @pnpm/wasm package, shrinks the pnpm package back to about 4 MB, and fixes pnpm publish with
This release moves the WebContainer build into a separate @pnpm/wasm package, shrinks the pnpm package back to about 4 MB, and fixes pnpm publish with provenance from GitLab CI.
The WebAssembly build for StackBlitz WebContainers now ships as a separate @pnpm/wasm package. The pnpm and @pnpm/exe packages no longer include it, which brings their unpacked size back from about 55 MB to about 4 MB. In a WebContainer, install @pnpm/wasm with npm to get the pnpm command.
pnpm publish with provenance from GitLab CI is no longer rejected by the npm registry with a 422 error. The provenance statement now includes the GitLab CI variables in invocation.parameters, as npm does #16551.
pnpm audit signatures now uses the TLS settings of the redirect target when a registry redirects its signing-keys request, for example to registry.npmjs.org. A cafile scoped to a private registry no longer makes the redirected request fail #16541.
Fixed pnpm install --frozen-lockfile rejecting an up-to-date lockfile when an injected workspace package uses a catalog entry in peerDependencies #16557.
The [<since>] filter selector works again with Git 2.24 through 2.27 #16561. With Git older than 2.24, the selector now fails with an error that names the required Git version.
It also detects changes in projects whose directory names contain non-ASCII characters. Such a change used to be credited to the parent project. changedFilesIgnorePattern and testPattern now match changed files whose names contain non-ASCII characters.
The pnpm executable is about 10% smaller. On macOS arm64 it went from 45.1 MB to 40.3 MB.
Sped up trust downgrade checks for packages with long release histories.
With optimisticRepeatInstall: false, pnpm install now runs the projects' own lifecycle scripts, such as prepare, even when node_modules is already up to date #16545.
pnpm self-update now fails for Homebrew-installed pnpm and prints the brew upgrade command for the installed formula, such as brew upgrade pnpm or brew upgrade pnpm@11. It used to install a second copy of pnpm that the Homebrew one kept shadowing #16547.
Nothing published for this version
pnpm 12.8.2 fixes a startup crash on Linux ppc64le and UnknownIssuer errors on systems without CA certificates. pnpm run no longer installs before eve
pnpm 12.8.2 fixes a startup crash on Linux ppc64le and UnknownIssuer errors on systems without CA certificates. pnpm run no longer installs before every script on CI when autoDedupe is enabled, and resolution and hoisted installs on macOS are faster.
Fixed pnpm crashing on startup on Linux ppc64le #16380.
Fixed installs failing with UnknownIssuer on Linux systems without CA certificates, such as node:24-slim, when NODE_EXTRA_CA_CERTS is set. The extra certificates now extend the bundled CA roots #16365.
pnpm now creates its store operation locks and other per-user lock files in $XDG_RUNTIME_DIR when it points to a directory only the user can write to. Otherwise, pnpm still uses /tmp on Linux and macOS. Sandboxes that block writes to /tmp can point XDG_RUNTIME_DIR at a writable directory #16390.
POSIX bin shims and the pnpm, pn, pnpx, and pnx launchers now run inside a Nix build, where the system default path holds none of the utilities they call. Installing again replaces the shims already in node_modules #16377.
In a project that pins another pnpm version, pnpm now passes a command with an option it does not know to the pinned version. Before, pnpm rejected the option before switching, so pnpm install --auto-dedupe failed with "Unknown option" even though the pinned pnpm supports it #16353.
pnpm install --frozen-lockfile now fails when Cargo.lock does not satisfy a dependency requirement in Cargo.toml. The error names the crate and the version the lockfile holds #16355.
pnpm install returns "Already up to date" again in a workspace with injected workspace dependencies and a shared lockfile. Since 12.7.0 every repeat install in such a workspace ran the full install and copied the injected projects again.
With injectWorkspacePackages: true, a fresh pnpm install now records a workspace dependency as link: when its injected copy differs from the project only by an optional peer that peer-dependent dedupe merges. It was recorded as a peer-suffixed file: copy #16354.
pnpm dedupe --check now passes right after pnpm dedupe when deduplication merges variants of a package that differ only in their peers. A lockfile key whose peer suffix named a merged variant now names the variant that replaced it #16356.
When minimumReleaseAge hides the version that latest points to, pnpm now falls back to a prerelease of the same major before a stable version of an older major. A stable version of the same major is still preferred. Before, while a new 1.0.0 was too new, latest fell back to an old 0.0.1 even though 1.0.0-beta.4 had been latest until then #16388.
Git-hosted dependencies now respect pmOnFail. If it is set to anything other than download, a git-hosted dependency that pins a pnpm version is prepared by the running pnpm, and pnpm does not download the pinned version #16376.
pnpmfile hooks such as readPackage now run once for a dependency that several packages request at the same time. They could run twice for it before.
childConcurrency now defaults to 5, the documented value. It used to be capped at 4 and to follow the host's CPU count.
pnpm run and pnpm exec no longer install dependencies before every script on CI when autoDedupe is enabled. pnpm install --frozen-lockfile now keeps the deduplication record left by an earlier install #16374.
On macOS and Linux, lifecycle scripts and pnpm run now always get PATH from the PATH variable. When the environment also held a Path variable, a script sometimes got Path's value, and failed with node: not found #16308.
pnpm run now exits after a SIGTERM in a container where pnpm is PID 1 and the script runs pnpm again, as "start": "pnpm serve" does. Since 12.6.0 it kept waiting after the script had shut down, until the container runtime killed it.
pnpm config get globalShims, pnpm shim list, and global installs no longer read globalShims from a project's pnpm-workspace.yaml. Only the global config file, the pnpm home's own pnpm-workspace.yaml, and PNPM_CONFIG_GLOBAL_SHIMS set it, so a repository cannot choose which globally installed packages get project-aware shims.
pnpm config set --location=project refuses a machine-level setting such as stateDir or scope with ERR_PNPM_CONFIG_SET_NOT_A_PROJECT_SETTING, which names where the setting belongs. pnpm config delete still clears such a key from a project's pnpm-workspace.yaml.
pnpm deploy no longer fails with ERR_PNPM_DEPLOY_AMBIGUOUS_PEER in a workspace with injectWorkspacePackages: true when a workspace package lists its peer dependency as a dev dependency too #16375.
pnpm deploy no longer copies the workspace root's packageManager and devEngines.packageManager fields into the deployed package.json #16403.
pnpm publish now includes bare README files and README files with Markdown extensions such as readme.markdown in registry metadata #12704.
pnpm store prune now removes the packages that only expired pnpm dlx cache entries used. They were left in the store until the next pnpm store prune #16383.
Sped up dependency resolution in large workspaces, and when many dependencies request different ranges of the same package. Resolution also uses less memory.
Sped up pnpm install with nodeLinker: hoisted on macOS when the lockfile is re-resolved, such as with autoDedupe enabled #16397.
Sped up extracting package tarballs.
pnpm install without --frozen-lockfile is faster on some machines in projects with a pnpm-workspace.yaml. Those installs linked with one worker thread per core, half of what a frozen install uses.
On Windows, warm pnpm install --frozen-lockfile runs are 4-5% faster on 4- and 8-core machines. pnpm now links with one worker thread per core on Windows, between 4 and 16. This changes frozen installs and installs in projects without a pnpm-workspace.yaml on machines with 3 to 15 cores.
pnpm 12.8.1 fixes pnpm install --frozen-lockfile rejecting lockfiles with injected workspace packages that have peers, restores the executable bit on
pnpm 12.8.1 fixes pnpm install --frozen-lockfile rejecting lockfiles with injected workspace packages that have peers, restores the executable bit on files of local directory dependencies, makes pnpm dedupe converge, and uses less CPU on many-core machines.
pnpm install --frozen-lockfile no longer rejects a freshly generated lockfile when an injected workspace package has peer dependencies #16332.
Executable files in a file: directory dependency or an injected workspace package keep their executable bit again. Since 12.8.0, pnpm installed these files without the permissions they have in their project.
pnpm dedupe now reaches a stable lockfile when a package's peer suffix is long enough to be hashed. Before, each run could switch that package's key between the hashed and the spelled-out suffix, so pnpm dedupe --check always failed #16331.
pnpm install --frozen-lockfile, the default in CI, now uses less CPU on machines with more than 8 cores. Warm installs on many-core Windows machines got up to 10% faster. Frozen installs now link with at most 16 worker threads.
verifyDepsBeforeRun no longer reports dependencies as outdated after a filtered install just because pnpm-lock.yaml has a newer modification time. It checks the lockfile against the packages that install put in place. Before, pnpm run reinstalled the whole workspace with lifecycle scripts on, for example after a Docker COPY brought in a lockfile with a newer mtime #16322.
After a filtered install, verifyDepsBeforeRun now also checks that the install put the selected projects' dependencies in place. A node_modules directory alone no longer counts as proof.
pnpm run and pnpm exec no longer install a project that has never been installed and has nothing to install. Such a project declares no dependencies, no peer dependencies that autoInstallPeers would fetch, and no install lifecycle scripts. The command now runs without writing node_modules or pnpm-lock.yaml #16313.
pnpm update -g --latest now upgrades globally installed packages beyond their saved version ranges #16320.
Nothing published for this version
Nothing published for this version
Nothing published for this version
pnpm now reports an unknown task setting in pnpm-workspace.yaml and carries on. It used to refuse to start, so a project could not use a task setting
pnpm now reports an unknown task setting in pnpm-workspace.yaml and carries on. It used to refuse to start, so a project could not use a task setting that only the pnpm version its packageManager pins reads. The setting is still an error when the running pnpm is that pinned version.
Python interpreter installation now retries historical release metadata requests. It caches the release list for up to 24 hours and refreshes it once after a lookup miss. When a release omits the current platform, the search samples at most eight other releases before reporting that the lookup is inconclusive.
Python registries entries now route packages by exact names or trailing-prefix patterns in packages. Registry declaration order no longer affects resolution. A matched package resolves exclusively from its assigned registry, including transitive and build dependencies. Use packages: ["*"] to declare the default index.
pnpm install no longer fails with "Too many levels of symbolic links" when a Cargo configuration file above the workspace is a symlink, such as a ~/.cargo/config.toml linked from a dotfiles repository.
pnpm install now returns "Already up to date" in a workspace where dedupeDirectDeps left a project without a node_modules directory of its own. Such a project forced a full install on every run.
pnpm install no longer refuses the repeat-install fast path just because a changed pnpm-lock.yaml is 16 MiB or larger. Such a lockfile forced a full install on the run after every change.
Nothing published for this version
pnpm 12.4.2 includes security fixes for executable shims and GitHub Actions links, more reliable installs, faster peer dependency checks in workspaces…
pnpm 12.4.2 includes security fixes for executable shims and GitHub Actions links, more reliable installs, faster peer dependency checks in workspaces, and Python lockfiles that work across compatible targets.
Dependency executables can no longer take over another package's POSIX bin shim through its shell helpers. Reinstall dependencies to replace existing shims #14837.
On Cygwin, MSYS2, and WSL, shims still use PATH for Windows path conversion, so dependency executables can still redirect them there.
GitHub Actions homepage links no longer expose server credentials. GitHub server URLs now require HTTPS, with HTTP allowed only for loopback hosts.
pnpm no longer crashes at startup on FreeBSD and other Unix-like platforms. Platforms other than Windows and macOS use ~/.local/share/pnpm/store by default #14859.
pnpm install on Windows no longer fails with ERR_PNPM_PACKAGE_MANAGER_REMOVE_MODULES_DIR when clearing node_modules containing linked dependencies, such as when changing nodeLinker #14790.
pnpm install <pkg> now accepts --prod and --dev, including --prod=false #14868.
pnpm install and pnpm update now honor --ignore-workspace in nested projects excluded from the surrounding workspace. The flag also skips that workspace's settings during the packageManager check #14809.
pnpm install on macOS no longer reuses stale files for file: tarball or git-hosted tarball dependencies.
pnpm install in a single-project directory now detects package.json edits made while the previous install was finishing #14890.
pnpm install --frozen-lockfile now removes packages no longer reachable from any project in pnpm-lock.yaml. This also prevents repeated lifecycle script execution and unnecessary installs before pnpm run and pnpm exec with verifyDepsBeforeRun #14891.
Node.js runtime resolution now reports network failures from unofficial-builds.nodejs.org. These failures previously omitted musl builds from pnpm-lock.yaml, making its contents depend on network access #14813.
pnpm install now rejects invalid peerDependencies specifiers with ERR_PNPM_INVALID_PEER_DEPENDENCY_SPECIFICATION. A value such as "foo": "foo@1.0.0" previously created a broken directory link #14791.
pnpm deploy now writes plain registry versions in the deployed package.json, without peer dependency suffixes. The lockfile retains peer bindings, and npm aliases retain their target package names #14873.
pnpm add <git repository> now names repositories without a package.json as @owner/repo, allowing dependencies on equally named repositories from different owners #14870.
Peer dependency resolution now deduplicates packages whose child dependency resolves an optional peer in only some workspace projects, such as next with styled-jsx's optional babel-plugin-macros peer #14800.
pnpm update now settles the lockfile in one run when an upgrade removes the package providing an optional peer dependency #14895.
pnpm update --no-save now preserves override-applied specifiers for dependencies it is not updating, preventing subsequent frozen installs from failing with ERR_PNPM_OUTDATED_LOCKFILE #14836.
pnpm update --no-save now succeeds under minimumReleaseAgeStrict when every resolved version is old enough #14835.
Workspace installs and pnpm peers check are faster when projects depend on each other, fixing a slowdown introduced in 12.3.0. Unmet peer dependencies of workspace packages are now reported only under projects that link them directly #14906.
Hoisted installs use less memory when packages are cached. Frozen-lockfile hoisted installs on macOS are also faster when reusable package directories are cached.
pnpm install --frozen-lockfile now reuses pylock.toml across compatible Python targets, including after kernel updates. Reuse requires unchanged requirements, index, and requires-python, compatible wheels, and a locked dependency graph matching the target's markers #14843.
The lockfile's environments marker now includes only the interpreter version and marker variables used by the dependency graph. Without --frozen-lockfile, pnpm warns and resolves again when the locked graph no longer matches the target.
Python resolution no longer fails on malformed Requires-Python values, such as the trailing comma in openpyxl 3.0.x. pnpm treats these releases as declaring no interpreter range #14910.
pnpm add pypi:... now rejects unsupported --save-prefix values before editing the manifest or resolving dependencies.
Scripts listed in syncInjectedDepsAfterScripts no longer fail with ERR_PNPM_INJECTED_DEPS_SYNC_READ_DIR when the lockfile contains an injected package copy that no project depends on.
shellEmulator now expands ${VAR}, ${VAR:-default}, and ${VAR:+alternative} in scripts #14814.
Cargo and Python project discovery now honors ! exclusions in pnpm-workspace.yaml packages, skipping both parsing and generated source configuration for excluded projects #14844.
pnpm --filter "./packages/{app,lib}" now selects either alternative. Brace alternatives can nest, span path separators, and combine with other wildcards.
GitHub Actions updates now stop if an action reference changes during version resolution, and preserve unrelated workflow edits.
pn, pnpx, and pnx now run the pnpm installed alongside them, even when that directory is absent from PATH or another pnpm comes first #14803.
pnpm --version now reports failures to install or record a project's pinned pnpm, then prints the running CLI's version. It also honors --store-dir and --store #14831.
pnpm self-update no longer reinstalls the active version when it was installed by the standalone installation script #14823.
pnpm t and pnpm tst work again as aliases for pnpm test.
pnpm sbom now emits valid repository URLs in CycloneDX externalReferences[].url and SPDX homepage. Shorthands such as vercel/ms become git+https URLs, embedded credentials are removed, and invalid repository values are omitted #14773.
pnpm 12.4.1 fixes installs that failed on filesystems refusing hard links or clones, on Android, and under nodeLinker: hoisted . Repeat installs are f
pnpm 12.4.1 fixes installs that failed on filesystems refusing hard links or clones, on Android, and under nodeLinker: hoisted. Repeat installs are faster.
pnpm install no longer fails with Operation not permitted when the filesystem refuses a hard link or a copy-on-write clone #14722. Under packageImportMethod: auto and clone-or-copy, pnpm copies the file instead. EdenFS checkouts, which have no hard links, and rootless containers, which refuse the clone syscall, both hit this. An explicit packageImportMethod: hardlink or clone still reports the error.
pnpm also copies a package file whose store entry has reached the filesystem's limit on names for one file, 1024 on NTFS and 65000 on ext4. Such a file failed the install under packageImportMethod: hardlink, and under auto it stopped pnpm hard linking for the rest of the install.
pnpm install no longer writes a package file through a symlink left at the path it is importing to. Copying such a file overwrote whatever the link pointed at, and created that file when the link pointed nowhere. An executable package file also made the link's target executable.
Fixed pnpm install and pnpm dlx on Android. Registry requests crashed because pnpm found no system CA certificates, so pnpm uses bundled ones there #14777. Imports also failed with "Permission denied" on filesystems that deny hard links and reflinks, and now fall back to copying #14780.
pnpm install no longer fails with "Invalid cross-device link" while preserving a package's nested node_modules directory during a Docker build #14758.
pnpm install no longer fails on a package tarball that carries a file at the archive root, such as the ._* entries macOS tar adds #14701. The file is installed at the root of the package.
A file: tarball packed without the usual package/ directory is now recorded under the name and version from its own package.json. It was recorded under the alias the dependency was given, at version 0.0.0.
Under nodeLinker: hoisted, pnpm install no longer re-imports packages that are already in place. A repeat install replaced the whole node_modules tree and reported Packages: +N. A package is still imported when its directory is missing, when its package.json no longer carries the installed version, when it is a file: dependency, and when it is patched. Lifecycle scripts no longer run again for a package left in place, and pnpm rebuild and a change to allowBuilds still reach it.
pnpm install now runs a dependency's build scripts again when its side-effects cache entry has no files to restore #14717. Such builds were skipped and nothing was put in their place, so a script whose whole effect lands outside its own package directory, such as a git hook installer, never took effect. pnpm no longer publishes empty artifacts to the shared side-effects cache either.
pnpm install, pnpm add, and pnpm dedupe now apply ignoredOptionalDependencies #14729. Matching optional dependencies are left out of the lockfile and are not installed. pnpm 12 installed them whenever it resolved dependencies from scratch.
pnpm install no longer links a transitive dependency to a workspace package when linkWorkspacePackages is true and the dependency is declared with a plain version range #14781. Enabling preferWorkspacePackages does not change this. Set linkWorkspacePackages: deep to link them.
pnpm install no longer leaves dangling dependency links in workspace packages located above the workspace root #14726.
pnpm install and pnpm add no longer leave a dangling symlink in node_modules when a project starts depending directly on a package that the lockfile holds only as a transitive dependency with resolved peer dependencies #14714.
pnpm dedupe now keeps a compatible auto-installed peer when another workspace project depends on a newer major #14697. Repeated runs alternated between compatible and incompatible peer versions.
pnpm peers check no longer reports a peer dependency declared as workspace:^, workspace:~, or a bare workspace: as unmet #14770. pnpm reported these as unmet whatever version the linked workspace project supplied.
Sped up repeat installs #14540. pnpm checks the store's files only for the packages it links into node_modules, instead of every package in the lockfile. Creating the command shims in node_modules/.bin makes about 1,500 fewer filesystem calls in a 76 project workspace. Installs that use the global virtual store read their slot paths from the cache directory instead of deriving them every time. Verifying a large lockfile also allocates less memory.
Sped up pnpm install in Cargo workspaces with many member crates. Repeated installs reuse verified Cargo checksum metadata.
Installing several packages from the same Git repository and commit now downloads the source once per install #14725. Each package still runs its prepare scripts in its own copy of the checkout.
pnpm now passes Ctrl+C on to the script or command it started and waits for it to shut down #14723. pnpm exited first, so a script that was still writing landed on the shell prompt.
pnpm run "/pattern/" --no-bail now lets every matched script finish after one of them fails #14718. The command exits with ERR_PNPM_RUN_FAILED, and its message lists the scripts that failed in the order they were selected.
pnpm pipeline no longer fails on a project that tracks a symlink, such as a CLAUDE.md pointing at AGENTS.md #14692. Changing a symlinked input's target invalidates that task's cache, and pnpm pipeline --no-cache no longer hashes task inputs.
pnpm add -g, pnpm update -g, and pnpm remove -g no longer change global bins or install directories after reading only part of an installed package group #13796. If any declared package manifest is missing, malformed, or unreadable, pnpm now fails before it activates or removes anything and leaves the existing global installation intact.
pnpm dedupe now processes every workspace project by default, including workspaces that keep a separate lockfile per project #14732. Workspace filters select which projects it processes, and --fail-if-no-match exits with an error when no project matches.
pnpm update <name>@<version> now keeps the range operator the manifest declares #14745. Running pnpm update react@19.3.0 on "react": "^19.2.8" writes "react": "^19.3.0". A jsr: entry keeps its jsr: prefix, and a plain pnpm update now moves a jsr: range the way it moves an npm range.
pnpm --filter directory selectors now support ? wildcards and character classes such as [ab]. A * or ? wildcard no longer selects a directory whose name starts with a dot, as on pnpm 11.
pnpm deploy --legacy now prefers the dependency versions pinned in the source workspace lockfile when they still satisfy the deployed project's range #13857.
pnpm sbom now leaves out a package's author field when the manifest author name is empty or contains only whitespace #14685. In a filtered or split workspace run, only a project with no author field inherits the workspace root's author.
pnpm sbom --sbom-format spdx now writes creationInfo.created with whole seconds, such as 2026-09-08T10:38:21Z #14684. The fractional seconds it carried were rejected by strict SPDX consumers.
The updateConfig pnpmfile hook now receives the resolved configuration, including settings that came from .npmrc, the command line, or a default #14676. Scoped registries are reported under registriesByScope, and a hook may rewrite that map to change where packages are fetched from. Registry credentials are reported under configByUri, as pnpm 11 reports them. An unset setting is left out rather than reported as null.
pnpm audit --fix and the minimumReleaseAgeStrict approval prompt now keep the comments in minimumReleaseAgeExclude when they append an entry to it in pnpm-workspace.yaml. The rest of the list is left as written, and the trustPolicyExcludePrune and minimumReleaseAgeExcludePrune cleanups keep the comments of the entries they retain.
pnpm install and pnpm dedupe now run those cleanups too #14759. Only pnpm add, pnpm update, and pnpm remove pruned the entries that the freshly written lockfile no longer resolves.
pnpm config set --global node-download-mirrors no longer rejects the key #13611. The global config file already accepted nodeDownloadMirrors, but the command refused to write it.
NO_PROXY entries that start with a dot, such as .npmjs.org, now bypass the proxy for the domain and its subdomains #14686.
pnpm no longer creates a project pnpm-lock.yaml when devEngines.packageManager.onFail is download and lockfile writing is off through lockfile: false or --no-lockfile #14728. pnpm still switches to the pinned version.
pnpm now writes node_modules/.package-map.json only when nodeExperimentalPackageMap is enabled. Nothing reads the file without that setting, and an install that stops writing the map removes the one a previous install left.
pnpm pipeline no longer fails with intermittent access denied errors when concurrent tasks save their cache entries on Windows.
Windows filesystem operations now retry permission errors for up to one second #14682. A permanent permission error delayed the failure by a minute. Sharing and lock violations keep their one minute retry budget.
pnpm now warns when the root package.json declares a non-empty workspaces array and the project has no pnpm-workspace.yaml #2255. Such an install linked no project and said nothing about why.
ERR_PNPM_PACKAGE_MANAGER_REMOVE_MODULES_DIR now names the file or directory in node_modules that pnpm could not clean up. It reported only the underlying OS error, such as "Access is denied (os error 5)".
pnpm --help no longer describes pnpm as experimental.
This tag was signed with the committer’s verified signature .
zkochan Zoltan Kochan
GPG key ID: 649E4D4AF74E7DEC
Verified Learn about vigilant mode .
Nothing published for this version
Sped up dependency resolution in large workspaces #14352 .
Sped up dependency resolution in large workspaces #14352.
pnpm 12 now accepts the boolean settings as command-line flags on every command that takes them in pnpm 11, for example pnpm install --unsafe-perm, pnpm add foo --offline, and pnpm install --dangerously-allow-all-builds. pnpm 12 rejected them with unexpected argument, which failed every install on Vercel, whose build runs pnpm install --unsafe-perm #14346.
pnpm remove now accepts --unsafe-perm, the same flag pnpm install, pnpm add, and pnpm update take.
This tag was signed with the committer’s verified signature .
zkochan Zoltan Kochan
GPG key ID: 649E4D4AF74E7DEC
Verified Learn about vigilant mode .
Fixed concurrent installs sharing a store occasionally failing with an ENOENT error while importing a package file #14353 .
Fixed concurrent installs sharing a store occasionally failing with an ENOENT error while importing a package file #14353.
Sped up writing the lockfile in large workspaces #14352.
Sped up dependency resolution in large workspaces #14352.
pnpm now runs through Node.js when it was installed by a tool that skips build scripts, such as Vercel's packageManager provisioning, Bun, Deno, or npm install --ignore-scripts. Those installs previously failed with syntax error near unexpected token ')'. They still cannot run pnpm on Windows. On macOS only a shell can start it #14346.
pnpm audit --fix update no longer aborts when a vulnerable package has no safe version inside its declared range #14508 . The run updates every packag…
pnpm audit --fix update no longer aborts when a vulnerable package has no safe version inside its declared range #14508. The run updates every package it can and lists the rest as remaining.
pnpm install no longer reruns root lifecycle scripts when the global virtual store contains an unfinished-build marker in a package slot that the current lockfile does not use pnpm/pnpm#14485.
Sped up installs that have no lockfile. pnpm now links packages whose dependency subtree has no peer dependencies into the virtual store while resolution is still running.
pnpm run and pnpm exec now start without reinstalling on filesystems that keep sub-millisecond mtimes, such as NTFS. Previously, every run on those filesystems reinstalled first pnpm/pnpm#14486.
pnpm import now keeps the versions recorded in package-lock.json, npm-shrinkwrap.json, or yarn.lock when it generates pnpm-lock.yaml. A range in package.json, a catalog, or an override still decides which versions are eligible, and the recorded version is preferred among them. The generated lockfile previously could pin newer versions than the source lockfile #14476.
pnpm import in a workspace now imports every workspace project into the shared lockfile. It previously imported only the project in the current directory.
pnpm import now fails with ERR_PNPM_LOCKFILE_NOT_FOUND when none of the three source lockfiles is present. It also fails with ERR_PNPM_YARN_LOCKFILE_PARSE_FAILED when it cannot parse yarn.lock. It previously generated a lockfile from scratch in both cases.
pnpm import always resolves locally. It warns when --pnpr-server or the pnpr-server setting is given and does not use the server.
Sped up installs in large workspaces. Discovering the workspace projects no longer enumerates every matched directory to learn which manifest files it holds #14352.
Sped up installs in large workspaces. The resolver and the peer pass allocate less for every dependency edge #14352.
pnpm self-update, pnpm with, and automatic package-manager version switching no longer wait through registry retry delays when a configured registry has no signatures and registry.npmjs.org is unavailable #14483.
Sped up installs in large workspaces. Saving the lockfile is faster, and the install finishes without waiting for memory cleanup #14352.
pnpm install now relinks workspace packages when publishConfig.linkDirectory changes. Frozen installs report an outdated lockfile until it is regenerated pnpm/pnpm#14488.
The pnpm npm wrapper keeps its placeholder shebang-less so pnpm 11 can install pnpm 12 through the version store. Wrapper installs must allow lifecycle scripts to install the native binary #14502.
Sped up dependency resolution when there is no lockfile, and for the dependencies a lockfile does not cover.
Sped up installs in large workspaces. Workspace link: targets and importer ids are now derived from the paths' suffixes under the workspace root #14352.
pnpm install now reports "Already up to date" when local tarball dependencies have not changed #14495.
pnpm update now accepts --ignore-scripts and skips lifecycle scripts during the update pnpm/pnpm#14512.
Sped up installs that restore a deleted node_modules from a warm global virtual store. pnpm no longer re-links packages that are already fully present in the global virtual store #14510.
Sped up installs in large workspaces: the anchor for re-rendering workspace link: targets is now derived once per project instead of once per dependen
Sped up installs in large workspaces: the anchor for re-rendering workspace link: targets is now derived once per project instead of once per dependency edge, and project ordering hashes paths by their raw bytes #14352.
After a self-update from pnpm 12.2 to 12.3, global commands such as node, npm, and yarn failed with unexpected argument '--shim' found. Global commands now launch normally, and their first launch migrates the global bin directory to native shims. When self-update downgrades to pnpm 12.2 or older, it keeps the newer native shims so those commands continue to work.
Sped up installs in large workspaces. The check that verifies each project against the lockfile now runs the projects in parallel #14352.
Nothing published for this version
Restored the pnpm executable target without a file extension so pnpm 12.1 and earlier can upgrade to newer pnpm 12 releases on POSIX systems.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Added pnpm cache path, which prints the directory pnpm uses for its metadata cache. CI setups can use it to cache that directory — including the lockf
Added pnpm cache path, which prints the directory pnpm uses for its metadata cache. CI setups can use it to cache that directory — including the lockfile verification log, which lets a job skip re-checking an unchanged lockfile against the configured supply-chain policies.
pnpm installs the other package managers now, not just itself: npm, Yarn Classic, Yarn Berry, Yarn 6 (yarnpkg/zpm), and Bun. Each is resolved and fetched through the trusted package-manager registries, and an npm-published one is verified against npm's signature for its exact version before it is executed.
Three things use it:
packageManager / devEngines.packageManager pin is honored, and a yarn.lock written by Yarn Classic no longer gets installed by Yarn Berry. pnpm provides that package manager when the dependency pinned a version, or when the host cannot satisfy what the dependency needs — so a repository built with Yarn now installs on a machine that has only pnpm, while a host that already has a suitable one keeps using its own.pnpm dlx (pnx) runs one of them for a single command: pnx yarn@4 install, pnx npm@11 ci, pnx bun@1.3.0 install. Naming a package manager, or a runtime (node, deno, bun), there now provisions the real thing instead of installing the npm package that shares its name — unless the specifier locates a package rather than asking for a released version (pnx yarn@npm:yarn@1.22.22, pnx yarn@yarnpkg/berry), which installs what it names — pnx yarn@4 was previously a missing version, since Yarn 4 is published as @yarnpkg/cli-dist, and pnx node@22 now runs that Node.js release rather than a wrapper that downloads one. --package naming a package manager picks which of its commands to run, so pnx --package npm@11 npx create-something runs that npm's npx.pnpm shim add yarn links a yarn command that runs whatever version the current project pins, and pnpm shim rm / pnpm shim ls manage those shims. It works for any package, not only package managers. Shims are never created as a side effect of pnpm setup or an install — a shim shadows the rest of your PATH, so pnpm only writes one when asked.Installing a package manager globally (pnpm add -g yarn) now makes it follow a project's pin too, the way a globally installed Node.js already follows devEngines.runtime: the pinned version runs where a project pins one, and the globally installed copy is the fallback everywhere else. An explicit globalShims entry, including false, is left as you set it.
pnpm add follows the same rule about what a name means. pnpm add -g yarn@4 installs Yarn Berry — it used to fail, because npm's yarn package stops at Classic — and pnpm add -g node@22 / pnpm add -g deno@2 install that Node.js or Deno release rather than a wrapper package that downloads one. In a project, naming a package manager records which one the project uses instead of installing it as a dependency, and naming a runtime records it under engines.runtime as node@runtime:22 already did.
The declaration goes where the package manager reads it. Yarn is started from a project pin by corepack, which reads only packageManager and only accepts an exact version there, so pnpm add yarn@4 resolves the line and writes "packageManager": "yarn@4.18.0" — the same thing corepack use yarn@4 writes, down to the +sha512.… integrity for the Yarn Classic line that corepack pins its tarball with. Every other package manager is recorded in devEngines.packageManager, which holds a range. Only one of the two fields is ever left behind: they declare the same thing, and corepack refuses to run a project whose declarations disagree.
A JavaScript package manager on a machine without Node.js gets a managed LTS runtime to run on.
What changes for a project coming from v11: pnpm add yarn records the project's package manager instead of installing the npm package that shares the name (that package is still reachable as pnpm add yarn@npm:yarn@1.22.22), pnpm add -g yarn installs the current Yarn line rather than Classic, pnpm add -g node / pnpm add -g deno and pnx node / pnx deno install a Node.js or Deno release rather than a wrapper package, and a globally installed package manager defers to a project's pin where there is one.
Resolving a Node.js runtime version (devEngines.runtime / runtime: specifiers) is now much faster: the per-version release metadata is cached in the pnpm cache directory after its signature is verified, and an exact stable version such as runtime:22.23.2 no longer downloads the Node.js release index. A pinned runtime whose metadata was fetched once resolves without any network access, which removes the noticeable delay on the first node invocation in a project pinning an already-downloaded runtime #13899.
Corepack can run pnpm 12 again #13018. Corepack installs no dependencies and runs no lifecycle scripts, so the native binary that the pnpm package normally receives from its platform-specific optional dependency was never there, and corepack use pnpm@next-12 failed with MODULE_NOT_FOUND. The package now ships the bin/pnpm.mjs and bin/pnpx.mjs entry points Corepack looks for; they fetch the pinned native binary on first use — verified against npm's signature and checksum, honouring COREPACK_NPM_REGISTRY and the rest of Corepack's registry environment — and hand over to it. Installing pnpm with a package manager is unaffected and still runs the binary directly, with no Node.js startup in between.
With a configured pnprServer, pnpm install skips the server exchanges it does not need, closing the gap where an up-to-date project paid a full resolve round trip that a direct install answered locally pnpm/pnpm#13904:
pnpm-lock.yaml still satisfies every manifest skips the server resolve exchange and materializes node_modules from the on-disk lockfile.lockfile-verified.jsonl cache already covers the lockfile under the current policy; server-verified and server-resolved lockfiles are now recorded into that cache.trustPolicy*, minimumReleaseAgeStrict, or minimumReleaseAgeExclude settings now invalidates the repeat-install fast path, matching the TypeScript CLI's workspace-state check.The published packages now ship a THIRD-PARTY-NOTICES.md file carrying the BSD 2-Clause license of the Yarn code that pnpm's hoisted-layout algorithm and built-in package-compatibility database are derived from.
Breaking change. Dependency cycles are now broken canonically during peer resolution: the members of each cycle are ordered by package id, and the edg…
Breaking change. Dependency cycles are now broken canonically during peer resolution: the members of each cycle are ordered by package id, and the edges that close a cycle are always cut at the same place, no matter where the installation walks into the cycle from. Previously the cut depended on the walk path, so installing the same dependencies could produce different lockfiles depending on importer order or resolution order #13846, and a peer-resolution verdict computed for one occurrence of a cyclic package could be wrongly reused at another #13865.
With canonical cycle breaking the lockfile is a pure function of the dependency graph: repeated installs, reordered importers, and reordered dependencies all produce byte-identical lockfiles. Peer dependencies of packages inside a cycle keep nearest-wins resolution along the canonical order, and a dependency edge that closes a cycle references an occurrence of its target resolved at the importer level. On large cycle-heavy workspaces peer resolution is 2–3× faster, uses about 25% less memory, and produces a substantially smaller lockfile (fewer redundant peer variants).
Existing lockfiles keep working: headless (--frozen-lockfile) installs consume them unchanged, and installs that skip resolution leave them untouched. The first install that actually re-resolves (for example after a dependency change) re-keys walk-order-dependent peer variants of cyclic packages once.
Auto-installed peer dependencies are no longer resolved to their lowest satisfying versions under resolutionMode: lowest-direct or time-based. A hoisted peer is not a dependency the project declares, so it resolves like a transitive dependency — to the highest version satisfying the peer range (under time-based, the highest within the publish-date cutoff) #13871.
The resolved dependency graph and lockfile no longer depend on the order in which workspace projects are listed or discovered: importers are processed in project-id order, so reordering the packages globs in pnpm-workspace.yaml (or any other change to project listing order) produces a byte-identical lockfile #13846. This also makes auto-installed peer placement, deprecation-warning attribution, and cycle back-edge bindings a function of the project set alone.
Fixed non-deterministic lockfiles on cold installs of projects with cyclic peer dependencies: resolved peer variants could silently drop from the lockfile depending on traversal order #13846, #13865.
A lockfile entry whose resolution is unchanged no longer loses its recorded deprecated marker when a registry serves the package's metadata inconsistently — re-resolving to the same version keeps the deprecation instead of silently dropping the line #13846.
pnpm update now writes the new version range back to package.json (and to the catalog: entry a dependency points at), instead of only updating the lockfile #13879. The range operator the dependency already declared is preserved, and a dependency declared through a dist-tag ("foo": "latest") keeps tracking the tag under both pnpm update and pnpm update --latest.
Added a new setting minimumReleaseAgeExcludePrune. When enabled, pnpm add, pnpm update, and pnpm remove prune the entries of minimumReleaseAgeExclude
Added a new setting minimumReleaseAgeExcludePrune. When enabled, pnpm add, pnpm update, and pnpm remove prune the entries of minimumReleaseAgeExclude in pnpm-workspace.yaml that the freshly written lockfile no longer resolves: versions that are gone are dropped (an entry is removed once none of its versions remain), and entries for packages that are no longer in the lockfile are removed too. Name patterns (@scope/*) are always kept. The cleanup is skipped when the install's lockfile does not cover the whole workspace (sharedWorkspaceLockfile: false), since entries another project still needs would look stale.
Renamed cleanupUnusedCatalogs to catalogPrune, so that catalog pruning and release-age exclude pruning use one vocabulary. cleanupUnusedCatalogs continues to work; when both are set, catalogPrune wins.
Added support for the syncInjectedDepsAfterScripts setting. It names the scripts after which every injected copy of the package that ran them is brought back in step with its source, so a build script no longer leaves the copies in the virtual store holding stale files.
pnpm add no longer re-resolves the dependency graph when pnpm-lock.yaml already holds a version satisfying the request — promoting a transitive dependency to a direct one, or adding to a second workspace package what a first one already depends on, now only saves the dependency in package.json and records its importer entry. A satisfying locked version is necessary but not sufficient: the install still falls back to a full resolution for a dist tag, an alias, a workspace:/catalog:/git/tarball specifier, --save-peer, an overridden package, a catalogMode other than manual, and — under resolutionMode: time-based or lowest-direct, which resolve a direct dependency to the low end of its range — a range several locked versions satisfy.
Global installs now switch over atomically. The command shims in the global bin directory point at a stable per-package link rather than at the directory a particular install produced, so pnpm add -g and pnpm update -g activate a new version by moving that one link instead of rewriting every shim. A command can no longer be missing from PATH while an install is in progress, and a failed install leaves the previous version in place.
pnpm audit --fix and pnpm audit --fix update no longer add minimumReleaseAgeExclude entries for patched versions that were published before the minimumReleaseAge cutoff. The publish time of each minimum patched version is now checked against the registry metadata, and only versions young enough to be blocked by the age gate get an exclusion entry #11563.
Bounded the number of requests in flight to the .pnpmfile.cjs worker process. An install that runs the readPackage hook for thousands of packages at once no longer risks failing with ERR_PNPM_PNPMFILE_FAIL on a hook timeout spent waiting in the queue rather than running the hook, and holds fewer copies of the manifests it is hooking while it waits.
pnpm add <pkg>@<version> and pnpm update <pkg>@<version> under catalogMode: strict no longer fail with ERR_PNPM_CATALOG_VERSION_MISMATCH when the catalog entry is a range that the wanted version satisfies. The dependency keeps using the catalog; only a version that really falls outside the catalog's range is rejected #13715.
Fixed pnpm install in CI to use frozen lockfile mode by default when an existing pnpm-lock.yaml is non-empty. An outdated lockfile now fails without being rewritten, while projects without a lockfile or with an empty lockfile can still generate one.
A changed catalogs or pnpm.overrides block no longer has to be the only change for pnpm install to update the lockfile in place. Editing an override while also removing a dependency, or changing a catalog entry in the same commit as a range bump, is now absorbed in one pass instead of re-resolving the whole dependency graph #13799.
Fixed the lockfile an in-place override update wrote when the overridden package was also a catalog entry: the entry kept the version it had before the override moved the package. The same could happen in reverse, when a catalog entry moved a package an override pins. Both cases now re-resolve instead.
pnpm install now updates the lockfile in place even when several kinds of changes happened since the last install — for example a removed dependency together with a widened ignoredOptionalDependencies list, or a dependency edit alongside a patch or settings change. Previously any combination of changes forced a full re-resolution #13763.
Resolving peer dependencies in a workspace whose dependency graph contains many peer-dependency cycles now needs less than half the memory and finishes about twice as fast. Verdicts computed inside dependency cycles are now cached and reused for the occurrences they are provably valid for, instead of being recomputed for every occurrence.
pnpm install and pnpm dedupe no longer eat all the available memory while resolving a graph in which many packages declare the same missing peer dependency, such as the react peer the @radix-ui packages share #13786.
With dedupeDirectDeps, a project's symlink that becomes redundant — because the workspace root started providing the same dependency at the same resolution — is removed on the next install instead of surviving forever #13775. The layout no longer depends on install history: an incremental install now ends up with the same node_modules a clean install of the same manifests produces.
pnpm deploy injects workspace dependencies again, so the deploy directory is self-contained instead of symlinking back into the source workspace #13754. Enabling injectWorkspacePackages with dedupeInjectedDeps disabled now also rewrites already-linked workspace dependencies to injected copies.
pnpm deploy --no-optional no longer writes a lockfile whose snapshots reference optional dependencies that the deploy excluded.
pnpm --filter . deploy deploys the project in the current directory instead of the projects nested under it, so deploying the workspace root now copies the root project and installs its workspace dependencies #13758. pnpm deploy --legacy no longer rewrites the source workspace's pnpm-lock.yaml.
Fixed pnpm install writing a different pnpm-lock.yaml for an unchanged project depending on the order its dependencies happened to resolve in, which showed up as spurious lockfile diffs between installs.
Removing the last dependency that references a catalog entry via the fast lockfile update no longer leaves the stale catalog entry in pnpm-lock.yaml.
--frozen-lockfile no longer rejects a lockfile pnpm just generated when packageExtensions adds a peer dependency to a workspace project. The peer is auto-installed and recorded in the importer entry, but the freshness check compared against the package.json on disk, which has no such peer, and reported the entry as a removed dependency #13836.
A git dependency whose clone (or shallow fetch) fails now reports which package it belongs to, under the ERR_PNPM_GIT_FETCH_FAILED code, with credentials in the repository URL redacted. When the lockfile records an SSH remote, the error also explains that fetching it needs an SSH key for that host, and that a lockfile entry written before pnpm v11.21 can be re-recorded over HTTPS with pnpm update <package> #13743.
An integrity recorded on a git dependency's resolution (resolution: {type: git, repo, commit, integrity: sha512-…}) is no longer treated as a checksum. pnpm never verifies a git checkout against such a hash — the commit pins the content — so it is now dropped when the lockfile is rewritten, and pnpm sbom no longer republishes it as a CycloneDX/SPDX checksum. Lockfiles carrying one also load again instead of failing with ERR_PNPM_BROKEN_LOCKFILE #13042.
pnpm sbom now also publishes the checksum of a type: binary runtime archive, which pnpm does verify.
A git dependency whose git ls-remote fails now reports the ERR_PNPM_GIT_RESOLVE_FAILED code, naming the dependency instead of printing a bare git invocation, with credentials in the repository URL redacted. A specifier that does not ask for SSH resolves over HTTPS, because the URL recorded in the lockfile has to work on every machine that installs it, so the error explains how to substitute the transport on a machine that can only reach the host over SSH (git config --global url."git@<host>:".insteadOf "https://<host>/") #13743.
A missing git executable is reported as one, instead of surfacing the raw failure to start the process.
Credentials embedded in a git specifier are redacted from the "Could not resolve <ref> to a commit of <repo>" errors too.
Resolving a public repository makes one git ls-remote round-trip instead of two.
pnpm install after moving a dependency between dependencies, devDependencies, and optionalDependencies now updates the lockfile in place instead of re-resolving the whole dependency graph #13696.
--ignore-pnpmfile is accepted again, on every command pnpm takes it on: install, add, update, dedupe, fetch, unlink, deploy, ci, and install-test #13808. The flag skips every pnpmfile hook the command would otherwise run: neither the workspace .pnpmfile.cjs nor the pnpmfiles of config dependencies are loaded, so no readPackage, updateConfig, afterAllResolved, custom resolver, or custom fetcher runs.
syncInjectedDepsAfterScripts now removes the bin link of a bin the script dropped. Previously only new bins were linked, so a build step that stopped declaring one left its shim behind, pointing at a command that was no longer there.
Fixed dependency resolution letting the order in which concurrent resolutions finished decide the outcome. When one package was reached from several places, whichever occurrence got there first decided the versions its dependencies were recorded at, so repeated installs of the same project could produce different pnpm-lock.yaml files.
Widening a dependency's range no longer leaves the project on an older version. The lockfile update now points the project at the highest version of that dependency already in the lockfile that satisfies the new range — matching what a full resolution records — instead of keeping the locked version whenever it happened to satisfy, which could leave a duplicate behind. A range change that only an already-locked version satisfies is now also handled without re-resolving #13778.
The lockfile's time: section is no longer dropped when pnpm-lock.yaml is rewritten. resolutionMode: time-based records each direct dependency's publish date there and now reads it back as the fallback for a package whose registry metadata carries no publish date, so a later resolution derives the same cutoff instead of picking different subdependency versions #13776.
resolutionMode is no longer ignored when minimumReleaseAge is in effect. lowest-direct and time-based pick the lowest satisfying version of a direct dependency again; previously any active release-age cutoff — including the built-in default — silently forced the highest, so resolutionMode only worked when minimumReleaseAge: 0 was set explicitly #13752.
Adding a package to a workspace no longer forces a full re-resolution when every dependency it declares is already locked for a sibling. The lockfile update writes the new project's importer entry from the versions the lockfile already holds; a dependency no locked version satisfies still reaches the resolver #13696.
Changing a pnpm.overrides entry to a version range now updates the lockfile in place when a version the lockfile already holds satisfies the range, instead of re-resolving the whole dependency graph. Only exact versions were handled before #13696.
pnpm add <pkg>@<version> and pnpm update <pkg>@<version> now move a catalog entry's resolution to the requested version. Previously, when the catalog entry was a range that covered the requested version but resolved to a different one, the request was dropped silently: nothing was installed, nothing was written, and no error was raised.
Changing a parent-scoped pnpm.overrides entry ("parent>child": "2.0.0") now updates the lockfile in place instead of re-resolving the whole dependency graph. Only the named parent's dependency moves; every other package keeps the version it had #13795.
Reduced peak memory usage while resolving peer dependencies. Workspaces with large, deeply peer-dependent dependency graphs could need gigabytes to install; the same install now needs meaningfully less.
Removing a dependency, or moving one to another already-locked version, no longer re-resolves the whole dependency graph just because some package resolves a peer with the same name. The lockfile update now compares the peer suffixes against the exact name@version the removal severed, so a suffix that names a different — still present — version of that dependency is left alone #13781.
pnpm install no longer re-resolves dependencies inside a subtree the lockfile pinned when another dependency reaches the same package. Those packages kept their locked versions in node_modules while pnpm-lock.yaml recorded newer ones, so an install could quietly move a transitive dependency — including across a major version — without anything asking it to.
A .pnpmfile.cjs readPackage hook that rewrites one of a project's own dependency specifiers is now honored: rewriting "is-positive": "^1.0.0" to 1.0.0 installs 1.0.0 and records specifier: 1.0.0 for the importer. Previously the hook was applied only to the manifests of resolved dependencies, so a project's own specifier resolved against the raw range from package.json #13769.
pnpm prune now prints the Scope: all N workspace projects line when run inside a workspace, as it prunes every project of the workspace.
Removing a package from a workspace now drops its importer entry from pnpm-lock.yaml, along with the dependencies only it needed. Previously the entry survived every later install, which kept those dependencies reachable and made the lockfile diverge from the one the TypeScript CLI writes #13783.
pnpm remove no longer re-resolves the dependency graph. The removed dependency's entries are dropped from pnpm-lock.yaml and anything they made unreachable is pruned, without registry access. The install still falls back to a full resolution when a surviving package resolves a peer dependency through the removed one.
An install sharing a global virtual store no longer removes an incomplete package directory that another importer is still writing, which could fail with failed to remove existing directory ... prior to swap: Directory not empty. Such a directory is now repaired in place, and a package file left damaged by an interrupted install is restored instead of being kept.
pnpm sbom no longer emits components for optional platform-specific dependencies that cannot be installed on the current platform (for example, the native @rolldown/binding-* variants for other operating systems). Such packages are present in the lockfile but are never downloaded, so their license (and other metadata) could not be resolved and they appeared in the SBOM without one. pnpm sbom --lockfile-only still describes the whole lockfile graph, which is platform-independent by design.
Kept unselected workspace link targets shallow during filtered isolated installs.
Reduced peak memory usage while resolving peer dependencies further: each occurrence in the dependency tree now shares its package id with the edge it came from instead of owning a copy of it.
An ssh:// git dependency pointing at a bracketed IPv6 host, such as ssh://[::1]/repo.git, is resolved now. Its colons were read as an SCP-style path separator, which turned the address into [:/1] and left the specifier unresolvable. Applies to both the TypeScript CLI and pacquet.
In the TypeScript CLI, an ssh:// git dependency written without user info — ssh://git.example.com/team/repo.git, git+ssh://git.example.com:2222/team/repo.git — no longer fails with TypeError: Cannot read properties of undefined (reading 'includes'). Only the user@host form worked before.
Commands in a project that pins a pnpm version no longer read the whole pnpm-lock.yaml to get at the leading env document. Reading stops at the end of that document, so the cost no longer grows with the rest of the lockfile: reading the env document out of an 8 MB lockfile takes ~15µs instead of ~390µs.
An install that drops the last dependent of a patched package no longer updates the lockfile in place and succeeds silently. Removing a dependency, widening ignoredOptionalDependencies, or adding a removal override could each prune the package while the patch stayed configured; such an install now falls back to a full resolution, which reports the unused patch with ERR_PNPM_UNUSED_PATCH. Under allowUnusedPatches, where the lockfile update is kept, the same install now warns that the patch went unused instead of saying nothing #13827.
Command shims on POSIX again exec a target that has no interpreter — a runtime binary such as the managed Node.js, or any bin without a shebang — inst
exec a target that has no interpreter — a runtime binary such as the managed Node.js, or any bin without a shebang — instead of waiting on it. A shim that waited reported a target killed by a signal as exit code 128+N (for example 137 for SIGKILL), so callers that distinguish a signal death from an exit code, such as CI runners and process supervisors, saw the wrong outcome.Globally installed bins can now follow the project you run them in. The new globalShims setting is a record of package names to policies that selects
globalShims setting is a record of package names to policies that selects which globally installed packages get project-aware shims; it defaults to { node: true, deno: true, bun: true } and merges key-wise, so globalShims: { bun: false } switches one default off and globalShims: { typescript: true } adds another package. With the default, a project that pins Node.js through devEngines.runtime or engines.runtime gets the pinned stable release — authenticated against the Node.js release-team signatures — downloaded on first use and run whenever you type node inside the project, with no shell hooks. Candidates that are not signature-verified (Deno, Bun, Node.js prereleases, and ordinary package bins you enable) ask "Do you trust this project?" once per candidate and remember the answer machine-locally; the record values name the policy per package: "auto" (or its shorthand true) defers to artifact authentication, "always" switches without ever asking (useful in CI), and "prompt" always asks, even for authenticated candidates. Set globalShims: false to disable the feature, or PNPM_SHIM_BYPASS=1 to bypass it for one invocation. On Windows, programs can keep spawning the global node.exe directly, without a shell.pnpm dlx and pnpm create no longer fail with "Failed to read patch file" in a project that has patchedDependencies. As in pnpm, the package dlx runs is installed unpatched.
Reduced the warm startup overhead of project-aware managed runtime shims.
ng build and nuxt build now work under the global virtual store: pnpm's built-in compatibility extensions add the tslib dependency that @angular/build uses without declaring and the unplugin dependency that @nuxt/vite-builder v4 uses without declaring.
The automatic packageManager version switch works again on registries whose tarball URLs point at a different host than the registry itself (load-balanced feed proxies, Artifactory-style mirrors). Package-manager entries are now always recorded with integrity-only resolutions — the download URL is derived from the trusted bootstrap registry instead — and entries persisted in an invalid shape by an earlier pnpm are discarded and re-resolved instead of failing every command #13619.
Registries that serve no npm signature metadata (private mirrors and feed proxies commonly strip dist.signatures) no longer break the automatic packageManager version switch and pnpm self-update #13147. When the configured registry cannot provide a verifiable signature, pnpm now fetches the signature from registry.npmjs.org and verifies it against the same embedded npm keys over the installed integrity — which proves exactly the same thing. If no signature can be obtained from either source (for example, both are unreachable, or the registry publishes only a shasum), pnpm proceeds with a warning instead of failing, but only when the packages resolve through a registry configured in the user's own (non-project) configuration; the download stays pinned by the lockfile integrity, and a signature that exists but does not validate still fails the switch.
pnpm setup no longer makes Node.js print a MODULE_TYPELESS_PACKAGE_JSON warning about dist/worker.js on every command. The package.json it writes next to a standalone executable now declares "type": "module".
Git dependencies on known hosts (GitHub, GitLab, Bitbucket) are now treated as identities rather than transport choices. Every representation of the s
Git dependencies on known hosts (GitHub, GitLab, Bitbucket) are now treated as identities rather than transport choices. Every representation of the same repository — github:owner/repo, owner/repo, git+https://…, git+ssh://git@… — resolves through the host's canonical HTTPS URL, and the lockfile never records an SSH URL for them. Repositories whose archive endpoint is anonymously reachable resolve to the host's archive (fast tarball download); all others resolve to a git clone of the canonical HTTPS URL, which every machine with access to the repository can fetch.
To reach a private hosted repository over SSH, configure the machine (not the project) with git's own URL rewriting, for example:
git config --global url."git@github.com:".insteadOf https://github.com/
pnpm shells out to git, so the rewrite applies to all of pnpm's git operations automatically. URLs of unknown hosts (self-hosted servers) are unaffected and keep their exact URL, including SSH. URLs with embedded credentials are also kept verbatim and never resolve to a host archive.
This removes the network probing that previously decided between HTTPS and SSH at resolution time, which could record a transport that only worked on the machine that happened to run the resolution (e.g. an SSH URL that broke CI runners without SSH keys).
Added interactive group selection to pnpm update --global --interactive.
pnpm root -g and pnpm bin -g now print warnings to stderr instead of stdout, so their stdout stays a clean, machine-readable path. Previously, running either command with --global in a project that pins a package manager (e.g. via the packageManager field) printed a warning like [WARN] Using --global skips the package manager check for this project ahead of the path, breaking programs that capture the output as a path #13672.
In pnpm 12, pnpm root -g and pnpm prefix -g are now supported (they previously failed with ERR_PNPM_CLI_ROOT_GLOBAL_UNSUPPORTED / ERR_PNPM_CLI_PREFIX_GLOBAL_UNSUPPORTED), and the reporter output of dlx, create, config, sbom, with, store, prefix, root, and bin goes to stderr, matching pnpm 11.
Fixed minimumReleaseAge fallback for custom dist-tags so the selected version does not exceed the registry’s original tag target.
Removing a dependency from package.json and reinstalling no longer re-resolves the dependency graph. The importer's entry is dropped from pnpm-lock.yaml, anything it made unreachable is pruned, and a catalog entry that loses its last referent is removed — all without registry access. Installs still fall back to a full resolution when a package that stays resolves a peer dependency through the removed one, since that would change the surviving package's entry rather than only prune.
Dependencies declared with an empty version range ("adler-32": "") install again instead of failing with ERR_PNPM_NO_MATCHING_VERSION #13673. An omitted range means "any version", as it does in npm and pnpm v11, so packages that publish one — such as js-xlsx, codepage, and ssf — no longer need an overrides entry to install.
Changing a catalog entry to a different exact version no longer re-resolves the dependency graph. The package is replaced in pnpm-lock.yaml directly, reusing the same check the pnpm.overrides fast path applies: every locked dependency of the package must still satisfy the new version's manifest. Installs fall back to a full resolution when anything other than the catalog reaches the package — an importer that depends on it directly, or another package that depends on it — since the graph would then need both versions.
Fixed installs under enableGlobalVirtualStore failing with failed to remove existing directory ... prior to swap: Directory not empty (or No such file or directory) when peer variants of an injected file: dependency hash to the same slot. The link pass now materializes each unique slot directory once instead of racing one force-mode import per peer variant against the same path.
The held-back-update warning printed by pnpm update no longer fires when minimumReleaseAge is the actual reason a newer version was not picked. The warning's baseline now applies the same maturity cutoff as the pick itself, so it no longer wrongly attributes the hold-back to "your manifests and already installed dependencies" or recommends an override that would defeat the age gate. See pnpm/pnpm#13071.
Changing autoInstallPeers, dedupePeers, peersSuffixMaxLength, excludeLinksFromLockfile, or injectWorkspacePackages no longer re-resolves the dependency graph when the lockfile proves the setting cannot affect it: no package or project declares a peer dependency for the peer settings, and no project depends on a directory or on another workspace project for the link and injection settings. The new setting is recorded in pnpm-lock.yaml and the install proceeds from the existing resolution. Every other case still falls back to a full resolution.
Adding, editing, or removing an entry in patchedDependencies no longer re-resolves the dependency graph. Resolution never reads a patch — it only records the patch file's hash against the package it matches — so the install now rewrites the affected entries in pnpm-lock.yaml and materializes the patched package from the store instead. Installs still fall back to a full resolution when the patched package is reachable as a peer dependency, and when the new configuration would leave a patch unused while allowUnusedPatches is off, so ERR_PNPM_UNUSED_PATCH is still reported.
pnpm install again records immature versions picked under minimumReleaseAge (when minimumReleaseAgeStrict is off) in minimumReleaseAgeExclude in pnpm-workspace.yaml, so a later frozen install of the same lockfile passes verification #13687.
Reduced peak install memory: cached registry metadata is now read on demand from the on-disk metadata cache instead of being held in memory for the whole resolution. Resolving a large peer-heavy graph (@teambit/bit) peaks at about 1.3 GB instead of 3.2 GB, and a full cold install of it stays under 2 GB #13681.
Lockfile verification now honors offline mode by using cached registry metadata instead of reaching the registry. When the required metadata is not available locally, verification reports the same ERR_PNPM_NO_OFFLINE_META condition used by offline resolution.
POSIX shell shims now follow symbolic links before computing basedir, preventing execution failures when a shim is invoked via an external symlink on PATH #13405.
Speed up installs after adding ignoredOptionalDependencies patterns by removing newly ignored optional dependencies and pruning packages that are no longer reachable without resolving the dependency graph again.
pnpm self-update no longer fails with the installed pnpm wrapper is missing when the global packages directory carries a pnpm-workspace.yaml of global settings (written there when a global install persists an allowBuilds decision). The engine install stays anchored to its own install directory instead of walking up and adopting that file as its workspace root. The pnpm dlx cache install gets the same anchoring, so a stray pnpm-workspace.yaml above the cache directory can no longer break it #13697.
Reduced peak memory usage and allocation churn during peer dependency resolution on workspaces with many peer-dependency issue occurrences #13681.
pnpm update without saving no longer records a version that the manifest's range excludes. The kept range stays authoritative: a requested version outside it is skipped with a warning, and a requested range, a dist tag, or --latest resolves within it instead of past it. Previously each of these could write a lockfile entry that contradicted its own specifier, which the next pnpm install --frozen-lockfile rejected with ERR_PNPM_OUTDATED_LOCKFILE #12764.
pnpm version -r --json now outputs [] instead of human-readable text when no pending changes exist pnpm/pnpm#13217.
On Windows, installation no longer fails with "A required privilege is not held by the client. (os error 1314)" when symlink creation requires elevation (e.g. Developer Mode is off) — pnpm now falls back to NTFS junctions in that case. Additionally, pnpm clean and pnpm deploy --force no longer fail with "Access is denied. (os error 5)" when removing the package links inside node_modules #13694.
Running pnpm setup, pnpm self-update, or a command that modifies the global installation (such as pnpm add --global) through sudo now fails with ERR_P
pnpm setup, pnpm self-update, or a command that modifies the global installation (such as pnpm add --global) through sudo now fails with ERR_PNPM_SUDO_NOT_SUPPORTED instead of silently operating on the root user's home directory. pnpm keeps global packages and configuration in the invoking user's home directory, so these commands never need root permissions. Read-only global commands (such as pnpm bin --global) still work under sudo.Archive entries whose paths use \ as a separator are now read the same way pnpm reads them. A nested path spelled bin\tool.js by Windows publishing tooling resolves to bin/tool.js, and a path traversal spelled with backslashes is rejected instead of being stored verbatim.
Fixed file: dependencies not being re-copied when their source directory changed. A file: dependency is copied into the store at install time rather than symlinked, so editing the local package's files and running pnpm install again left the previous copy in place — the lockfile is unchanged by such an edit, so the install treated the tree as up to date.
Write blocked-build approval scaffolding to the discovered workspace manifest when using per-project lockfiles.
Concurrent installs sharing a global virtual store no longer fail with failed to remove existing directory ... prior to swap: Directory not empty, and no longer briefly remove a package directory another process is reading.
Fixed link: dependencies under enableGlobalVirtualStore so linked children are materialized and slots remain isolated by their resolved link targets.
A headless install (--frozen-lockfile) now creates the command shims for a publicly hoisted workspace package's bin, matching what a normal install already did and what pnpm's own headless install does. Previously those shims were missing until the next non-frozen install.
pnpm fetch, and any install run with virtualStoreOnly, no longer writes a .pnp.cjs loader under nodeLinker: pnp. These installs populate the virtual store without linking the project, so the loader would have claimed the project resolves out of a store it was never linked into. The importer links and node_modules/.package-map.json were already skipped; the PnP loader now follows the same rule.
Prevent pnpm from removing project files when modulesDir resolves to the project root.
Fixed pnpm install ignoring a pnpm-lock.yaml that carries a leading env lockfile document when the file has CRLF line endings or a UTF-8 byte order mark, as a core.autocrlf checkout on Windows produces. The lockfile was reported as broken with multiple YAML documents detected and every dependency was re-resolved from the registry #13606.
When no directory above the project accepts a hard link — inside an AI agent sandbox that only grants write access to the project, or a container with just the project mounted writable — the default store is now created at <project>/node_modules/.pnpm-store instead of in the pnpm home directory. In those environments the home store is either read-only or on another volume, which forces every package to be copied instead of hard linked #13525.
A stray non-directory entry in node_modules no longer fails an install. Files placed next to the installed dependencies are skipped rather than reported as an unreadable manifest.
Security fix. Affects projects using namedRegistries on pnpm 11.1.0–11.19.x. It is semi-breaking for those projects — see "If you use named registries…
Security fix. Affects projects using namedRegistries on pnpm 11.1.0–11.19.x. It is semi-breaking for those projects — see "If you use named registries" below.
The lockfile recorded no marker for which registry a package came from. Packages were keyed by name@version alone, and entry lookup went through refToRelative(ref, name), so a dependency you declared against one registry could be satisfied by an entry that was actually resolved from another. When two registries served the same name and version, both collapsed onto a single packages: entry and whichever resolved first decided the tarball every consumer got.
That is a package-substitution risk: a package you expect from your private registry could be installed from a different registry that publishes the same name and version, and the lockfile recorded nothing that would let you tell.
Packages resolved from a named registry are now recorded under registry-qualified keys (<name>@<registryName>:<version>, e.g. foo@work:1.0.0), so each registry gets its own entry and the lockfile pins which one a dependency came from.
The lockfile format version is unchanged. Registry-qualified keys appear only for packages resolved from a named registry, so a project that does not use namedRegistries sees no difference, and older pnpm versions keep reading the file.
Your next non-frozen install re-keys those entries, which shows up as a lockfile diff. Commit it — that diff is the fix being applied. Review it: an entry that moves to a registry you did not expect is worth investigating.
Everyone working on the project should be on this version or newer before you do. An older pnpm reads the re-keyed lockfile fine — frozen installs are unaffected — but it does not produce registry-qualified keys itself, so any install that updates the lockfile writes those entries back to the old shape, and the next install on a current pnpm re-qualifies them. The result is a lockfile that flips back and forth, and while it is in the old shape the project is exposed again. Because the lockfile format version is deliberately unchanged, pnpm cannot detect this and warn you about it.
There is no setting to keep the old behavior: the old shape is the vulnerability.
Tarball URLs that follow the standard registry layout are no longer written to the lockfile for named-registry packages; they are recomputed from the namedRegistries setting on demand.
To use named registries, map your aliases in pnpm-workspace.yaml:
namedRegistries:
work: https://npm.enterprise.example.com/
npmjs: aliasnpmjs: now resolves to https://registry.npmjs.org/ with no configuration, alongside the existing gh: alias for GitHub Packages. It pins a dependency to the public registry even when registry points elsewhere, such as an internal proxy:
{ "dependencies": { "left-pad": "npmjs:^1.3.0" } }
npm: cannot do this — it is the alias protocol (npm:<name>@<range>) and resolves through whatever registry points at.
If you mirror or proxy npmjs, point the alias at your mirror:
namedRegistries:
npmjs: https://npm.internal.example.com/
Built-in registry URLs are also the prefixes a lockfile's recorded tarball URL is matched against when pnpm verifies a package. Without the override, an entry whose tarball URL is on registry.npmjs.org is verified against the public registry rather than your mirror. This only affects lockfiles that record such URLs — a canonical URL for your configured registry is omitted from the lockfile and unaffected — and only when a tarball-URL, minimumReleaseAge, or trustPolicy check runs. Overriding the alias is the same escape hatch GHES users already have for gh.
Every alias the lockfile references must stay in namedRegistries: reading an entry whose alias is gone fails with ERR_PNPM_MISSING_NAMED_REGISTRY rather than silently falling back to the default registry, since that would fetch a different package. Renaming an alias re-resolves the packages that used it.
Named registry aliases that shadow a reserved dependency specifier prefix (file, link, workspace, runtime, npm, jsr, ...) are now rejected with ERR_PNPM_RESERVED_NAMED_REGISTRY_NAME instead of being silently shadowed by the corresponding resolver.
pnpm licenses and pnpm sbom now keep the two artifacts apart as well: license records carry the registry alias, and SBOM components carry the purl repository_url qualifier.
Installing a workspace whose projects auto-install peer dependencies is substantially faster. Each round of the peer-hoist loop no longer scans the whole workspace once per project, so the cost of resolution grows with the workspace instead of with its square.
Installing a dependency chain whose packages carry peer dependencies no longer expands exponentially with the depth of the chain. A single project with a single such dependency could exhaust memory before finishing; it now resolves in tens of megabytes.
Fixed non-deterministic resolution on multi-project workspaces: two consecutive installs of the same inputs could bind peer-suffixed packages to different (still valid) providers, rewriting pnpm-lock.yaml on every install #13567.
Installing a workspace now produces the same pnpm-lock.yaml every time. Two installs of the same workspace could previously bind a peer dependency to a different — still valid — version, which changed the lockfile without anything in the project changing.
An empty http-proxy, https-proxy, proxy, or no-proxy value — from the .npmrc, pnpm-workspace.yaml, the CLI, or the HTTP_PROXY / HTTPS_PROXY / PROXY / NO_PROXY environment variables — no longer fails the install with ERR_PNPM_INVALID_PROXY. Empty settings read as unset, so a shell exporting HTTP_PROXY= disables the proxy, and an empty proxy= in the .npmrc no longer suppresses HTTPS_PROXY #13533.
proxy=false in the .npmrc or proxy: false in pnpm-workspace.yaml now turns proxying off instead of being read as a proxy host named false. false and null on https-proxy / http-proxy / no-proxy read as unset, and on the command line they are ordinary host names, since a flag carries its value verbatim.
The env lockfile no longer pins @pnpm/exe alongside pnpm when the wanted pnpm version is 12 or newer. From v12 the unscoped pnpm package is itself the native executable, so @pnpm/exe is not published for it and resolving it would fail. The engine identity check now verifies the native binary through whichever package ships it.
Resolution on large peer-heavy workspaces got faster: a Bit workspace with 114 projects and ~21,000 lockfile entries resolves in ~13.4s instead of ~16.0s. The resolved dependency graph is unchanged.
Fixed nondeterministic peer bindings in large multi-project workspaces.
Resolving a workspace whose dependency chains are deep is faster: deciding which missing peer dependencies another project's resolution already covers now answers once per shared chain segment instead of once per report.
Peer resolution on large workspaces got faster: each hoist round now refreshes its view of the dependency graph from what the round changed instead of re-reading every resolved package. The resolved dependency graph is unchanged.
pnpm install no longer crashes on a machine whose system certificate store is empty or absent — for example a minimal container or build sandbox that ships no CA certificates #13588. Such a system now falls back to the Mozilla root certificates bundled into the binary, the same set Node.js ships, so both offline and online installs work again. Certificates from the system store, NODE_EXTRA_CA_CERTS, and the .npmrc ca / cafile settings keep taking precedence whenever any of them is available.
Fixed the order in which pnpm matches a lockfile's recorded tarball URL against known registry URLs. Two registry URLs of equal length were previously ordered arbitrarily, so which one a tarball URL matched could differ between runs.
pnpm login / pnpm adduser now read the scope setting from pnpm-workspace.yaml, the global config.yaml, and the PNPM_CONFIG_SCOPE environment variable, not only from the --scope command-line flag. When scope is configured, the granted token is keyed to that scope and the scope-to-registry mapping is recorded. --scope still takes precedence when both are set. Note that scope in an .npmrc is not read — pnpm keeps only auth and registry keys from that file.
Resolution spends less time in its final peer pass: the package-name cycle graph it consults is now derived once per package instead of once per occurrence of that package.
npm's --prefix is accepted as a spelling of --dir, and --store as a spelling of --store-dir, so pnpm --prefix ../ run test no longer fails with "unexpected argument '--prefix' found" #13583.
pnpm now ships node-gyp again, so packages whose install scripts shell out to it build out of the box. Previously they failed with spawn node-gyp ENOENT unless a node-gyp was already on PATH — affecting node-gyp-build with no matching prebuild, node-pre-gyp, a plain "install": "node-gyp rebuild", and any package shipping a binding.gyp without an install script. As in pnpm 11, the whole node-gyp dependency tree is resolved from pnpm's own lockfile when pnpm is released, so it is frozen per release rather than resolved on your machine, and npm_config_node_gyp, a workspace node-gyp, and a package's own node-gyp dependency all still take precedence.
With excludeLinksFromLockfile enabled, a link: dependency pointing inside the workspace is no longer treated as an external link when it resolves a pe
With excludeLinksFromLockfile enabled, a link: dependency pointing inside the workspace is no longer treated as an external link when it resolves a peer dependency, so the peer suffixes it produces stay identical to an install with the setting off. Injected (file:) workspace dependencies are no longer affected by the setting either #13556.
A registry dependency is now always recorded in pnpm-lock.yaml with an integrity hash, including under --lockfile-only. Packages from a registry that publishes no subresource-integrity metadata — https://node-registry.bit.cloud/, for one — were recorded without one, so the next pnpm install --frozen-lockfile failed with ERR_PNPM_MISSING_TARBALL_INTEGRITY #13547.
Dependency resolution is faster: package metadata is now filtered once per packument instead of once per dependency edge when minimumReleaseAge is active, and parsed semver versions and ranges are reused instead of re-parsed on every comparison.
Reduced memory use when resolving peer-heavy dependency graphs and prevented nested hoisted graphs from expanding into excessive dependency paths.
pnpm install no longer fails when pnpm-lock.yaml exists but cannot be parsed. Matching the TypeScript CLI, the install now prints an "Ignoring broken
pnpm install no longer fails when pnpm-lock.yaml exists but cannot be parsed. Matching the TypeScript CLI, the install now prints an "Ignoring broken lockfile" warning, resolves dependencies from the manifests, and rewrites the lockfile. --frozen-lockfile still fails on a broken lockfile.
Fresh installs no longer download the tarballs of platform-specific optional dependencies that don't match the current platform.
Aligned deprecated package warnings with pnpm by reporting each package only on its first resolution and shortening direct dependency warnings during…
Made peer resolution significantly faster in large multi-importer workspaces (a 114-importer workspace's resolution dropped from ~77s to ~36s): importers whose hoist rounds converged no longer re-walk their dependency forest every round, later rounds walk only newly added direct dependencies, ownership handovers with an unchanged peer context no longer invalidate shared walk caches, and the resolver's internal hash maps use a faster hash. Peer dependencies provided by multiple candidate versions may resolve to a different (still range-valid) provider than before, which can shift some peer-variant suffixes in pnpm-lock.yaml once.
pnpm login no longer requires an interactive terminal when the registry supports web-based login: without a TTY it prints the authentication URL (skipping the QR code and the "Press ENTER to open the URL in your browser" prompt) and polls the registry until the browser approval completes. Only the classic username/password login still fails with ERR_PNPM_LOGIN_NON_INTERACTIVE in a non-interactive terminal.
Added projects[].dependencyManifest to the @pnpm/napi install options: the manifest a workspace project exposes when it is resolved as a dependency of another importer (an injected instance). Hosts that pre-transform their importer manifests no longer need a readPackage hook to substitute the raw manifest, and per-manifest deletions are expressed through the existing overrides removal syntax ("pkg": "-"), so resolution can run without any JS round trips.
The save-prefix setting now accepts =: newly added dependencies are saved with an explicit = operator (=1.2.3) instead of the setting being silently treated as the default ^.
allowBuilds entries can now approve git-hosted packages that pnpm downloads as a tarball, such as github: dependencies (which are fetched from codeload.github.com rather than cloned), by their repository URL without the resolved commit hash. This matches the hashless git+ matching already supported for cloned git dependencies. For example:
allowBuilds:
"foo@git+https://github.com/org/foo.git": true
This approves the package whether pnpm clones it or downloads a tarball, so the entry no longer has to be updated every time the pinned commit changes. GitLab and Bitbucket tarball downloads are matched the same way. Approving or denying a specific resolved commit by its full tarball dep path continues to work.
Fixed a severe slowdown resolving large workspaces against registries whose abbreviated metadata lacks per-version time fields (such as node-registry.bit.cloud) while minimumReleaseAge is active. The resolver upgraded the abbreviated packument to full metadata once per dependency edge instead of once per package — re-requesting the same packument from the registry hundreds of times in a single install — and a 304 Not Modified answer was never remembered, so the round trip repeated forever. The upgrade outcome is now cached for the rest of the install. On a 345-project workspace this cut a full resolution from 105 s to 36 s.
Also stopped the resolver from deep-copying every workspace project manifest on each internal resolve-options clone (the workspace-packages map is now shared by reference).
Aligned deprecated package warnings with pnpm by reporting each package only on its first resolution and shortening direct dependency warnings during recursive installs.
pnpm outdated --include-github-actions no longer blocks on an interactive git credential prompt when a workflow uses a private action repo.
pnpm add and pnpm update now honor the saveExact setting; previously only the --save-exact flag was respected.
Fixed parsing very large lockfiles that exceed the YAML parser's default 64 MiB scalar-text budget.
Fixed parsing large lockfiles that exceed the YAML parser's default structural budget pnpm/pnpm#12857.
Fixed writing lockfiles with dependency paths longer than 1024 characters (long peer suffixes in large workspaces): such keys are now emitted in explicit ? <key> form, matching the TypeScript CLI. Inline keys of that length are invalid YAML, so pnpm could not re-read the lockfile it had just written and every subsequent install re-resolved from scratch.
Prevented minimumReleaseAge from replacing latest with a SemVer-greater version than the registry tag target #13034.
Installs driven through @pnpm/napi got three fixes for large workspaces: the readPackage hook is now dispatched to JavaScript in batches instead of one event-loop roundtrip per manifest, the dedupePeers setting can be passed through the install options (so an existing lockfile generated with it is no longer treated as outdated), and version-pinned dependencies are served from the metadata mirror without queueing behind concurrent registry refreshes of the same package.
Fixed the overrides block of pnpm-lock.yaml being rewritten in a random order on every install performed through @pnpm/napi. The recorded overrides now keep the order they were declared in, so repeat installs no longer churn the lockfile.
The TRACE environment variable now enables engine tracing for @pnpm/napi consumers the same way it does for the pnpm CLI, and an invalid TRACE filter no longer aborts the process — it prints a warning and leaves tracing off.
Fixed peer resolution creating far more peer variants than the TypeScript CLI in multi-importer workspaces: a dependency subtree first resolved under one importer no longer hands the peer providers it resolved to every other importer that shares it. Those importers now bind such peers against their own context (or the workspace root), matching the TypeScript resolver. In a large bit.cloud workspace this cut a from-scratch install from 25,534 to 20,791 lockfile snapshots.
Fixed empty bundledDependencies and bundleDependencies arrays causing nondeterministic lockfile changes. See pnpm/pnpm#13123.
Reduced the peer resolution pass's CPU cost on workspaces with many peer dependencies. The walker cloned its parent peer-context maps at every node — twice per node plus once per child — even when a node contributed nothing to them; the maps are now shared copy-on-write and the derived per-child snapshots are reused unless the context actually changed. On a peer-heavy 331-importer benchmark the full resolution dropped from 3.9 s to 2.8 s.
The install summary no longer prints (X is available) when the registry's dist-tags.latest is still held back by the active minimumReleaseAge policy. The hint only ever names the actual latest tag, so an immature latest suppresses the hint instead of advertising the version pnpm just refused to install #11698.
pnpm update keeps the explicit = operator of an exact version pin: a dependency saved as =3.5.1 now updates to =3.5.2 instead of the bare 3.5.2. See pnpm/pnpm#13168.
Preserve a workspace dependency's link: entry when a run does not target it — e.g. pnpm update <other-pkg> (with or without --recursive), or a plain install after a root/catalog dependency change — with injectWorkspacePackages, instead of spuriously rewriting it to a peer-suffixed file: protocol. See pnpm/pnpm#10433.
Fixed resolution of a direct dependency declared in both dependencies and devDependencies: the dependencies specifier now wins, matching the TypeScript CLI. The devDependencies range was resolved instead, recording a lockfile importer entry whose version did not satisfy its specifier — which failed the lockfile up-to-date check and forced a full re-resolve on every install.
Kept the lockfile policy verdict ahead of the frozen-install message when package statistics arrive while verification is still running.
overrides are now applied after the readPackage hook during resolution, matching the TypeScript CLI's hook order (packageExtensions → readPackage hooks → overrides). A hook that replaced a manifest — such as a host application substituting a workspace project's raw manifest for its injected instances — previously erased the overrides from that manifest, so the resolved graph ignored them.
Sped up multi-importer resolution by sharing the run-resolved preferred-versions fold across importers. Every importer replayed the whole workspace's resolved-versions history into a private map each hoist round — O(importers × packages) map inserts and string clones — although the peer-hoist pickers only ever look up a handful of missing-peer names. The fold is now maintained once, workspace-wide, and importers materialize just the buckets they query. Full resolution of a 331-importer benchmark workspace dropped from 886 ms to 424 ms (peer-heavy variant: 2.8 s to 2.4 s).
Fixed quadratic time and memory use when resolving a large multi-project workspace from scratch. Resolving a workspace with hundreds of projects sharing thousands of packages previously took minutes and several gigabytes of memory; it now completes in seconds.
Breaking change from pnpm v11. Under engineStrict, an install fails when an incompatible package is reached through a regular dependencies edge of an…
The Rust engine now reads four more settings from pnpm-workspace.yaml and PNPM_CONFIG_*, instead of only accepting them as CLI flags:
frozenLockfile — pnpm install grows a --no-frozen-lockfile flag so the setting can be overridden in both directions. As in pnpm, it cannot be set in the global config.yaml.savePrefix — the range operator pnpm add saves, still overridable with --save-prefix / --save-exact.savePeer — pnpm add also records the new dependency in peerDependencies. pnpm add --no-save-peer overrides it back off.saveCatalogName — the catalog pnpm add saves into.The Rust engine now supports the saveWorkspaceProtocol setting, so pnpm add <pkg>@workspace:… writes back the same specifier pnpm does. Under the default rolling, a request like workspace:^1.2.3 is saved as workspace:^ — a range with no version in it, so bumping the workspace package never has to touch its dependents' manifests. saveWorkspaceProtocol: true saves the workspace package's resolved version instead (workspace:^2.5.0), and false keeps the workspace: form only when it was asked for explicitly. Previously the specifier was written back exactly as typed.
pnpm update --workspace is supported: dependencies that a workspace project publishes are re-pointed at the local copies through the workspace: protocol. The saveWorkspaceProtocol setting is honored — under its rolling default an entry becomes workspace:*, workspace:^, or workspace:~ (whichever matches the range it already declared), so a sibling's next release does not invalidate it. Naming a dependency that is not in the workspace fails with ERR_PNPM_WORKSPACE_PACKAGE_NOT_FOUND, and combining the flag with --latest fails with ERR_PNPM_BAD_OPTIONS.
pnpm update --depth <number> is now applied per dependency instead of only distinguishing 0 from higher values: a dependency deeper than the given depth keeps its locked resolution, so pnpm update --depth 0 updates direct dependencies only.
pnpm run "/^build:(backend|frontend)$/" selects every script whose name matches the pattern, in single-project and recursive runs alike #13322. Flags on the selector are rejected with ERR_PNPM_UNSUPPORTED_SCRIPT_COMMAND_FORMAT, as pnpm does.
pnpm self-update no longer takes any instruction from the project it is run in:
.npmrc or pnpm-workspace.yaml can no longer redirect the download or attach credentials to it, and the project's default .pnpmfile.(c|m)js is no longer loaded. Pnpmfiles from trusted sources (the pnpmfile setting, the global pnpmfile, config dependencies) still apply.minimumReleaseAge settings in pnpm-workspace.yaml no longer affect self-update. They still govern the project's own dependencies; for self-update the cooldown now comes from the built-in default, your global config, a PNPM_CONFIG_* environment variable, or a command-line flag. This fixes self-update failing inside a workspace that raises the cutoff while succeeding everywhere else, and stops a repository from either waiving the cooldown or keeping you on an outdated pnpm by raising it.trustPolicy settings and to ci: a project can no longer weaken the trust check that guards the pnpm download, nor re-enable the confirmation prompt that a CI run suppresses.When self-update refuses a version that is younger than the cutoff, an interactive run now offers to update anyway; non-interactive runs still fail. CI never prompts, even on a runner that attaches a TTY.
An aliased dependency of a protocol that resolves under its own package name — jsr: and the named registries — is recorded in the lockfile importer again. "bar-from-jsr": "jsr:@pnpm-e2e/bar@1.0.0" resolved and installed, but the importer stayed empty, so nothing reading direct dependencies out of the lockfile (outdated, update, licenses, dedupe, frozen-install verification) could see it #13362.
An allowBuilds entry with the set this to true or false placeholder pnpm scaffolds no longer makes every command in that workspace fail with a config-parse error #13322. An undecided entry now leaves the package under the default-deny build policy, as pnpm does.
Two pnpm install resolution fixes that made large workspaces such as Astro produce a different pnpm-lock.yaml than pnpm 11 #13334:
file: protocol ("@test/pkg": "file:./pkg") is recorded as a link: again instead of being copied in as a file: snapshot.bundledDependencies / bundleDependencies are no longer resolved as dependencies of their own. npm ships them inside the package's tarball, so installing them again added packages the lockfile should not contain (for example napi-wasm under @parcel/watcher-wasm).Executables that a package ships inside its own tarball (bundledDependencies) are linked again into that package's node_modules/.bin, under both the isolated and the hoisted node linker. A package that declares bundleDependencies: true instead of a list of names is now recorded in pnpm-lock.yaml the way pnpm 11 records it, and such a lockfile can be read back.
Aligned pnpm licenses list --json package metadata and license-group ordering with the TypeScript CLI.
Fixed pnpm --filter <package> run to list the selected package's scripts and root workspace scripts when no script name is specified.
Strip Unicode formatting characters from registry- and manifest-derived terminal output.
Prevented dependency verification before scripts from rewriting an up-to-date lockfile.
Concurrent commands in a repository that pins packageManager no longer race while installing the pinned pnpm version on a cold cache #13322. A task runner spawning several pnpm run children at once could previously fail with "failed to remove existing directory … prior to swap", or leave a child looking for a binary another process had just unlinked.
Config-load warnings, such as the warning about install settings left under the pnpm field of package.json, are printed to stderr instead of stdout #13361.
pnpm dedupe --check now reports what deduplication would change: the importer and package snapshot diff, the ERR_PNPM_DEDUPE_CHECK_ISSUES error code, and the warning that points at pnpm peers check when the install leaves peer-dependency issues behind. pnpm peers check is also accepted again — the subcommand spelling used on pnpm.io and in pnpm's own dedupe output — instead of failing with "unexpected argument 'check' found" #13321.
A deprecated package is reported once rather than once per workspace project that depends on it, and is no longer double-counted in the "deprecated subdependencies found" summary when it is also a direct dependency #13322. Ignored build scripts are also listed with their (patch_hash=…) suffix, so two copies of a package that differ only by an applied patch are distinguishable.
pnpm install --frozen-lockfile no longer re-imports a varying subset of packages on every repeat install of an unchanged project #13316. The global-virtual-store directory of a package that takes part in a dependency cycle was derived from an order that changed from run to run, so those packages landed on a fresh slot each time; it is now derived deterministically and matches the directory pnpm itself computes.
When the pinned packageManager engine install cannot take its lock because the store cannot be written to, pnpm now reports that instead of quietly installing without the lock. A lock another process holds is unchanged — it is still waited for.
Speed up installs after compatible catalog or direct dependency range changes by retaining the locked version without resolving the dependency graph again.
Speed up installs after safe override changes by reusing unambiguous compatible dependency resolutions, pruning obsolete dependencies, applying independent replacements and removals together, and handling parent-scoped "-" overrides without full lockfile resolution.
Fixed warm side-effects cache reuse for git dependencies.
Aligned pnpm dedupe --check progress and error output with the TypeScript CLI.
A frozen install now fails when autoInstallPeers, dedupePeers, or excludeLinksFromLockfile has changed since pnpm-lock.yaml was written, instead of installing against a lockfile that no longer matches the settings. The error names the drifted setting, as pnpm install --frozen-lockfile has always done.
A repeat pnpm install --frozen-lockfile is a no-op again when the project has a platform-incompatible optional dependency. The skipped package is kept in node_modules/.pnpm/lock.yaml (.modules.yaml is what records the skip), so the install can once more recognize an unchanged tree instead of re-running every lifecycle and dependency build script #13312.
Reject frozen installs when the current pnpmfile does not match the lockfile's pnpmfileChecksum.
Fixed pnpm licenses list to report dependencies from every workspace project, exclude unsupported platform packages, and mark development dependencies.
Setting both autoInstallPeers: false and dedupePeerDependents: false now leaves missing peers alone, instead of still installing the ones a version elsewhere in the workspace could satisfy.
Installing a local file: directory dependency with the global virtual store enabled no longer fails with TypeError: Cannot read properties of undefined (reading 'split') #13335.
Local directory dependencies — file: directories and injected workspace packages — now get a global-virtual-store slot of their own per project. They used to share one slot across every project that depended on a directory of the same name, so a project could end up linked to another project's copy of the dependency.
A missing required peer is no longer auto-installed as a prerelease that its declared range rejects. A package peer-depending on ^29.0.0 || ^30.0.0 next to a 30.0.0-alpha.6 pulled in elsewhere in the graph now resolves a stable 29.x/30.x from the registry instead of adopting the alpha #13341.
A lockfile entry for a git-hosted archive that records no integrity installs again instead of failing with ERR_PNPM_MISSING_TARBALL_INTEGRITY. Older pnpm versions wrote that shape for dependencies like "ci-info": "watson/ci-info#f43f6a1c…", so any committed lockfile still carrying one could not be installed #13308. The archive URL pins a full commit SHA, and pnpm fetches it without an integrity check.
Every other remote tarball still has to carry an integrity, and the refusal now points at the repair: pnpm clean --lockfile followed by pnpm install.
Error output no longer repeats the same message once per level of the internal error chain.
The Workspace column of pnpm update --interactive now falls back to the project's path when its name is only whitespace, as it already did for a missing or empty one — all three render an equally blank label otherwise.
pnpm update --interactive now groups the dependencies it offers by dependency type — dependencies, devDependencies, optionalDependencies, peerDependencies, and GitHub Actions each get their own heading — and lays each group out as a column-aligned table with a Package/Current/Target/URL header, instead of one flat list.
pnpm update --interactive now measures its table in terminal columns rather than in characters. A package name, workspace name, or version containing wide characters (CJK, most emoji) no longer knocks its row's columns out of line with the rest of the group, and a wide character in a version no longer aborts the command with Subject parameter value width cannot be greater than the container width #13357.
pnpm update --interactive run inside a workspace now shows a Workspace column naming the project each outdated dependency was found in, so the same package outdated in several projects can be told apart.
Write single-value libc package metadata in the same scalar form as pnpm.
Fixed pnpm install silently skipping a local file:*.tgz dependency: the package is now extracted into the virtual store, recorded under packages: and snapshots:, and linked into node_modules #13379.
A frozen install whose recorded settings no longer match the configuration — overrides, catalogs, patchedDependencies, and the rest — now fails with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH naming the one setting that changed, instead of ERR_PNPM_OUTDATED_LOCKFILE with the whole map dumped #13322.
Fixed pnpm install dropping a package that ships no package.json of its own from the lockfile. Such a package is now named after its alias and recorded at version 0.0.0 under packages: and snapshots:, and its extraction gets the placeholder package.json pnpm writes #13410.
Resolve optional peers from versions provided by local workspace packages, omit empty deprecation messages from generated lockfiles, and preserve valid lockfile pins in pnpm dedupe --check.
Aligned license reports, dedupe --check progress and spacing, and dependency-verification output with pnpm.
A file: dependency declared by a package that was itself installed from a local directory is now resolved relative to that package's directory, not to the importer's #13323. Installing a project whose local dependency depends on a sibling directory (file:../child) no longer fails with Could not install from "…" as it does not exist, and the snapshot entry for such a dependency is now written as file:<path> instead of <name>@file:<path>, matching the lockfile pnpm writes.
npm_config_user_agent now carries the configured user agent (pnpm/<version> …) in install lifecycle scripts, pnpm run, pnpm exec, and pnpm dlx #13322. It was previously unset for install scripts and the bare string pnpm elsewhere, which made preinstall guards that check for pnpm reject the install.
An auto-installed optional peer is no longer hoisted at a version the workspace root's own dependency on that package excludes. resolvePeersFromWorkspaceRoot already made the workspace root's specifier decide which version a missing required peer is installed at; the optional-peer picker ignored it and always took the highest version present anywhere in the graph. In a workspace whose root pins postcss: 8.5.10, an importer that depends on webpack and declares no postcss of its own got postcss@8.5.22 hoisted for terser-webpack-plugin's optional postcss peer, leaving two postcss@8.5.x instances in the graph #13320.
A missing optional peer dependency is no longer satisfied by a prerelease version that its declared range doesn't accept. ts-jest, which declares @jest/transform and jest-util as optional peers with ^29.0.0 || ^30.0.0, was bound to 30.0.0-alpha.6 when a jest 30 prerelease was elsewhere in the graph, while jest itself stayed on 29.
With autoInstallPeers: false, a package's own optional peer dependencies are no longer added to its importer entry in pnpm-lock.yaml (and no longer linked into its node_modules) when another workspace project happens to resolve a matching version #13325.
overrides now also govern peers that pnpm auto-installs. Previously an override only rewrote dependencies declared in a manifest, so a peer nobody declares — installed because autoInstallPeers is on — resolved against its declared peer range and could bring in a second copy of the very package the override pinned. For example, with overrides: { react: npm:react@19.2.0 } and a lone lucide-react dependency, pnpm installed react@18.3.1; it now installs the pinned react@19.2.0 #13320.
pnpm approve-builds -g is accepted again, reporting that the command is not supported with global packages rather than failing with unexpected argument '-g' found. approve-builds was the only command that declared --global without its -g short form #13310.
The Rust implementation of pnpm has moved from alpha to beta releases.
A catalog name containing a control character no longer corrupts pnpm-workspace.yaml. pnpm add --save-catalog-name "$(printf 'a\nb')" (or the same value in saveCatalogName) now fails with ERR_PNPM_WORKSPACE_MANIFEST_WRITER_INVALID_CONTROL_CHARACTER and leaves the file untouched, matching how the writer already treats allowBuilds and overrides entries.
Preserve each direct dependency's locked optional peer context during pnpm dedupe.
Preserve optional peer providers recorded in peer suffixes when pnpm dedupe rebuilds a workspace lockfile.
With nodeLinker: hoisted, a workspace project no longer gets its own copy of a dependency whose version already won the workspace-root slot. Only the versions that lost the root slot are nested, matching the pnpm CLI. Previously every project's direct dependency was materialized under that project as well, which gave lifecycle scripts a second copy to run in.
The Rust engine now warns when package.json still declares install settings under the pnpm field, which pnpm 10 moved to pnpm-workspace.yaml. A project that hasn't migrated its pnpm.overrides / pnpm.packageExtensions / pnpm.patchedDependencies previously saw the settings silently ignored, and only met the downstream symptom. Keys the pnpm field never owned are left alone.
Fixed pnpm licenses list and pnpm licenses ls parsing and license metadata discovery when using the global virtual store pnpm/pnpm#13332 and pnpm/pnpm#13333.
A pnpm-workspace.yaml that declares a package pattern whose directory does not exist yet — packages/* before the first package is created, say — no longer fails every command with ERR_PNPM_WORKSPACE_WALK_ERROR. The pattern now matches no projects, as it does in the JavaScript implementation #13296.
Fixed catalog: references failing to resolve when installing through a pnpr server, which errored with "No catalog entry '<name>' was found for catalog 'default'." even though the catalog entry existed. The workspace the server reconstructs from the request has no catalog sections, so the client now sends its catalogs along with the request #13232.
Validate the project's pinned package manager and runtimes before running a command, matching the pnpm CLI:
packageManager / devEngines.packageManager pin that the running pnpm does not satisfy now fails with ERR_PNPM_BAD_PM_VERSION (or ERR_PNPM_OTHER_PM_EXPECTED when the project is pinned to another package manager), instead of being silently ignored. The check also runs under corepack, where pnpm cannot switch versions itself, and says so.devEngines.runtime / engines.runtime entries with onFail: "error" or onFail: "warn" are validated against the Node.js, Deno, or Bun installed on the system, failing with ERR_PNPM_BAD_RUNTIME_VERSION.pmOnFail and runtimeOnFail are honored as bypasses and can now be passed as --pm-on-fail=<value> / --runtime-on-fail=<value>, the form the error hints suggest.Global commands (--global) and commands that do not belong to the project (store, dlx, self-update, …) skip these checks, as does a project pin that only asked pnpm to switch versions when manage-package-manager-versions is turned off.
Arguments after the script or command name now reach the script untouched for pnpm run, pnpm exec, pnpm dlx, and pnpm with, matching the JavaScript implementation. Previously pnpm run build --config.foo=bar consumed the argument as a pnpm setting instead of forwarding it, and pnpm run build --silent handed the script --reporter=silent — a token the user never typed #13302. Put such flags before the script name (pnpm run --silent build) to apply them to pnpm.
Fixed generated lockfiles to preserve packages' scalar libc constraints.
Preserve a user-provided TMPDIR when scripts run with unsafePerm enabled; otherwise, continue using the package-local temporary directory.
Added support for publishConfig.name, which publishes a package under a different name than the one its manifest carries in the workspace. Only the published artifact is renamed — dependents, pnpm-lock.yaml, and release tooling keep addressing the project by its manifest name — and the new name reaches the packed manifest, the tarball filename, and everything that addresses the package at the registry: the already-published check of pnpm publish -r, its registry selection, and the release-planning probes of pnpm change status and pnpm version -r. This also fixes the changelog of the Rust CLI itself, which is published as pnpm from a workspace project named pacquet: its release notes were composed under the workspace name and so never made it into the published package #13345.
pnpm run <script> <args> now forwards every argument after the script name to the script verbatim, matching the behavior of the JavaScript implementation. Previously the -- separator was dropped, so pnpm run test -- --watch reached the underlying program as --watch and failed whenever that program claimed the option itself; arguments spelled like pnpm run's own flags (-s, --if-present) were also consumed by pnpm instead of reaching the script #13295. Pass those flags before the script name (pnpm run -s test) to apply them to pnpm.
pnpm test, pnpm start, and pnpm stop now forward their arguments to the script, matching the pnpm CLI. pnpm test --watch and pnpm start --port 3000 previously failed with a usage error, and pnpm stop claimed --if-present and -s for itself instead of passing them on. As with pnpm run, every token after the command name reaches the script verbatim, a -- separator included.
Added the --workspace-root (-w) flag, which runs the command on the root workspace project. pnpm add -D typescript prettier -w from a workspace subdirectory now saves to the root package.json instead of failing with "unexpected argument '-w' found" #13031. Combined with --recursive, the flag narrows the run to the root project alone. -w may not be used together with --global, and may only be used inside a workspace.
patchedDependencies patch files that pnpm applies no longer fail with ERR_PNPM_PATCH_FAILED: a hunk whose last line is context in a file with no final newline, and an LF patch against a CRLF file, both apply again #13322. A hunk that has drifted from its recorded line numbers is also retried nearby, matching pnpm.
A peer dependency is now recorded in the lockfile at the version and peer suffix the peer provider actually resolved to. Peers whose provider carried peer suffixes of its own could be recorded against a package instance that no importer installs, leaving an unreachable entry in snapshots: and a peer bound to the wrong instance #13320.
A peer dependency that the workspace root already provides is no longer installed a second time. With resolvePeersFromWorkspaceRoot enabled (the default), a missing peer is matched against the workspace root project's dependencies; it was matched against the dependencies of whichever project was being resolved, so a project that didn't declare the peer itself resolved its own copy from the registry. In vercel/next.js, whose overrides pin react to a single canary build, this pulled in a second react and paired it with react-dom from the canary — a combination the pin exists to prevent.
Under resolvePeersFromWorkspaceRoot, a workspace root dependency declared with link: or file: (or the path form of workspace:, such as workspace:../pkg) now satisfies another project's missing peer dependency at the linked package's own version, instead of being hoisted as a path. Those specifiers are relative to the project that declares them, so the same specifier reached a different directory — or none — from the project the peer was hoisted into, leaving a broken link. The root now has the same authority over the peer as it has when it declares the package with a version range #13373.
Two pnpm install peer-resolution fixes that made large workspaces such as Astro produce a different pnpm-lock.yaml than pnpm 11 #13334:
dependencies and peerDependencies no longer gets a nested copy of it when the parent already supplies that name, which is what pnpm does with autoInstallPeers disabled. The nested copy hid the peer, so the package was recorded without the peer context it resolves in.Closed the remaining gaps in how unscoped per-registry .npmrc settings are pinned to the registry their own source file declared:
cert= / key= written with \n escapes now expands to a real multi-line PEM, matching the URL-scoped //host/:cert= spelling.pnpm config get / pnpm config list now report a rescoped credential under the URL-scoped key it was pinned to, instead of the unscoped key it was written as.tokenHelper.@pnpm/napi bindings: the authHeaderByUri entry written with an empty ("") key is pinned to the registry / registries.default the host passed alongside it, never to a registry the project's .npmrc names.The projects that run their own lifecycle scripts (preinstall, install, postinstall, prepare, …) now match pnpm in every install-family command. A project runs them when the command installs it in full, and — in a workspace the command only partly covers — whenever the command mutates it at all; the workspace root runs them even when the command was pointed at another project, because it is installed in full alongside it. As a result, pnpm update <pkg> and pnpm add <pkg> in a workspace no longer skip the workspace root's scripts, pnpm update at a workspace root no longer runs the other members' scripts, and pnpm update --latest no longer runs the project's own scripts (it rewrites named dependency specs, so it is a partial install like pnpm update <pkg>) #13358.
Print the script command by default when running a filtered lifecycle script. The command remains hidden with --silent.
Run dependency verification consistently after regenerating a lockfile with dedupePeers enabled.
Aligned the hoistedDependencies contents and ordering in node_modules/.modules.yaml with pnpm.
Preserve whether package libc metadata uses a string or an array when writing the lockfile.
A package.json that starts with a UTF-8 byte order mark is read again instead of failing with expected value at line 1 column 1. Workspace discovery, dependency manifests (including bin linking), tarball extraction, and pnpm publish of a pre-built tarball all accept one, matching pnpm #13311. A manifest that really is malformed now reports its path in the error.
Fixed ERR_PNPM_BROKEN_LOCKFILE when installing with a pnpm 10 lockfile that has a patchedDependencies section. See pnpm/pnpm#13307.
pnpm -r run "/pattern/" --no-bail no longer exits zero when one of a project's matched scripts fails and a later one passes. The run summary carries a single status per project, and the passing script overwrote the recorded failure.
Resolution failures now report the error pnpm defines for them. A well-formed range that the registry publishes nothing for fails with ERR_PNPM_NO_MATCHING_VERSION — naming the latest release, the other dist-tags, and the pnpm view <pkg> versions command that lists the rest — instead of ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER. A package the registry doesn't have fails with ERR_PNPM_FETCH_404 and the "not in the npm registry, or you have no permission to fetch it" hint (plus which authorization header was sent, since a private registry often answers a permission failure with a 404) instead of a bare HTTP-client message. A wrapper that quotes its cause verbatim no longer prints the same sentence twice in the error report.
Fixed pnpm add <workspace-package> to resolve the local package when linkWorkspacePackages is enabled.
$dep-name self-references in overrides are now resolved against the root manifest's direct dependencies, so an override such as rolldown: $rolldown records the concrete specifier in pnpm-lock.yaml and no longer fails a frozen install with ERR_PNPM_OUTDATED_LOCKFILE #13314. A reference to a package that is not a direct dependency fails with ERR_PNPM_CANNOT_RESOLVE_OVERRIDE_VERSION, and the deprecated syntax now warns, pointing at catalogs.
The root project's pnpm:devPreinstall script now runs before resolution and linking, as it does in pnpm 11. It is skipped under --ignore-scripts, --lockfile-only and --dry-run, by pnpm fetch and pnpm rebuild, and by a repeat install that is already up to date. Workspaces that use the hook to prepare state the install depends on — such as next.js, which generates a placeholder next bin with it — were left with dependents linked against files that were never created #13313.
The lockfile-verification line now dates a cached verdict — ✓ Lockfile passes supply-chain policies (verified 253ms ago) — instead of the timeless (previously verified) #13315.
pnpm install, run, test, update, remove, link, unlink, prune, and rebuild now print the workspace scope they resolved — Scope: all 41 workspace projects, or Scope: 5 of 41 workspace projects under a --filter. This is the confirmation that a filter selected what was intended #13315.
An install that blocks a dependency's build scripts now appends a placeholder for it to pnpm-workspace.yaml, so approving or denying the build is an edit rather than writing the block by hand:
allowBuilds:
es5-ext: set this to true or false
A placeholder is not a decision — the build stays blocked until it is replaced with true or false — and an existing entry is never overwritten #13315.
Fixed --parallel being treated as the script name when placed before run in a recursive command.
scriptShell now selects the shell for lifecycle scripts too — dependency build scripts and a project's own preinstall/install/postinstall/prepare and pnpm:devPreinstall — not only for pnpm run and pnpm exec. A workspace that configures a shell was still getting the platform default (sh / cmd) for everything the install itself spawns.
Fixed shamefullyHoist: true to create public root dependency links.
Prevented optional peers from being selected from an unrelated workspace package's shared dependency context.
The store index now keys URL, git-host, and type: git dependencies by their bare resolution id, matching the key pnpm 11 writes #13365. Previously these rows carried a <name>@ prefix, so a store warmed by one pnpm major was cold for the other and every non-registry dependency was re-downloaded, re-extracted, and re-imported on a switch. A remote tarball also occupied two index rows instead of one, doubling its extraction work.
A project that pins pnpm through devEngines.packageManager (or a v12+ packageManager field) now gets its packageManagerDependencies recorded in pnpm-lock.yaml by every command, not just by the install-family ones #13348. Running pnpm list (or any other command) in a freshly cloned project no longer leaves the lockfile without the pinned version. The pmOnFail setting now also decides whether the pin is recorded: --pm-on-fail=ignore keeps it out of the lockfile even when the manifest asks for a stricter policy, and vice versa.
Fixed pnpm licenses list to detect licenses from license files and preserve the latest package version's development classification.
pnpm update --latest now rewrites jsr: dependencies. The manifest keeps the protocol and the range operator it declared, so jsr:1.0.0 becomes jsr:2.0.0 and jsr:@scope/name@^1.0.0 becomes jsr:@scope/name@^2.0.0, instead of being left at the old version #13363.
pnpm update --latest now rewrites dependencies using a named registry alias. The manifest keeps the alias prefix and the range operator it declared, so gh:1.0.0 becomes gh:2.0.0 and gh:@acme/foo@^1.0.0 becomes gh:@acme/foo@^2.0.0, instead of being left at the old version pnpm/pnpm#13393.
Breaking change from pnpm v11. Under engineStrict, an install fails when an incompatible package is reached through a regular dependencies edge of an installable package, even when that whole subtree hangs off an optionalDependencies entry. pnpm v11 installs the package and emits an install-check warning instead. Packages reachable only through optional edges, or through a package that was itself skipped, are still skipped in both versions #13286.
A lockfile entry whose tarball resolution records no integrity is now reported by the lockfile-verification gate, before anything is downloaded: every offending entry is listed in one ERR_PNPM_MISSING_TARBALL_INTEGRITY error instead of failing the install one fetch at a time after the gate had already passed the lockfile #13364. An integrity: '' that pins nothing is treated the same as a missing one, and the exemption for git-host archive URLs is now read from the URL rather than the lockfile's own gitHosted marker.
Fixed pnpm licenses list to read licenses from legacy package manifest fields.
Fixed --workspace-root (-w) selecting the current workspace when --dir pointed at a nonexistent directory outside it (for example pnpm --dir ../../elsewhere add -w foo). The command now fails with ERR_PNPM_NOT_IN_WORKSPACE, matching pnpm. A nonexistent --dir inside the workspace still resolves to the workspace root as before.
pnpm setup now appends PNPM_HOME and the global bin directory to the GitHub Actions environment files (GITHUB_ENV and GITHUB_PATH), so later steps in
pnpm setup now appends PNPM_HOME and the global bin directory to the GitHub Actions environment files (GITHUB_ENV and GITHUB_PATH), so later steps in the same job can run pnpm add --global and other global commands #9191.Checking GitHub Actions dependencies for updates is now opt-in for every command. Neither pnpm outdated nor pnpm update reads the workflow files unless --include-github-actions is passed or update.githubActions is set to true in pnpm-workspace.yaml. Reading them runs git ls-remote against every referenced repository, which fails in environments where GitHub is not reachable the way pnpm assumes (a GitHub Enterprise Server, a custom certificate authority, or an offline network) #13254.
pnpm outdated accepts the --include-github-actions option too.
pnpm update --latest now keeps dependencies that the npm registry does not serve in the form they were declared. A runtime: dependency (such as "node": "runtime:26.5.0"), a git/github: URL, or a remote tarball URL previously had its name looked up on the npm registry and its specifier overwritten with that unrelated package's version.
pnpm update --latest also no longer rewrites package.json when a dependency is already at its latest version.
Close three CLI parity gaps with the TypeScript pnpm CLI:
Close three CLI parity gaps with the TypeScript pnpm CLI:
--registry <url> is now accepted on every command as a universal rc-option, not only through --config.registry=<url> (pnpm view pnpm dist-tags.latest --registry=https://registry.npmjs.org/).pnpm add (and pnpm add -g) now accept --allow-build=<pkg>, appending the named packages to allowBuilds so they can run their lifecycle scripts during the install (pnpm add @pnpm/exe@11.16.0 --allow-build=@pnpm/exe).--dir / -C is now position-independent: it is accepted anywhere on the command line, before or after the subcommand (pnpm add foo --dir /tmp/proj).pnpm publish --provenance now applies the fetch-timeout setting to the sigstore signing exchange and retries it up to two more times with exponential backoff when it fails or times out, instead of aborting the publish on the first transient network error or hanging on a stalled connection.
pnpm update --latest now resolves a dependency declared through an npm: alias — directly in package.json or in the catalog entry a catalog: reference points to — to the latest version of the aliased package, keeping the npm:<name>@ prefix in the rewritten specifier. Previously the alias name itself was looked up on the registry, failing the update with a 404 when no package of that name exists.
Nothing published for this version
The deprecated updateConfig, auditConfig, and auditLevel settings keep working until the next major version. When both a new section value and its dep…
The first release of a package now publishes the version written in its manifest verbatim, instead of bumping off it. pnpm version -r and pnpm change status check the registry for each release's current version; when that version is not yet published, the package debuts at it and its pending changesets apply only from the next release. A newly added package seeded at 1100.0.0 with a minor changeset is therefore published as 1100.0.0 rather than skipping straight to 1100.1.0.
pnpm version now supports the npm-style bump forms: pnpm version <major|minor|patch|premajor|preminor|prepatch|prerelease> and pnpm version <exact-version> (also recursively with -r), with --preid, --allow-same-version, --message, --no-git-tag-version, --no-commit-hooks, --sign-git-tag, --tag-version-prefix, and --json. The bump runs the preversion/version/postversion lifecycle scripts and records the new version as a git commit and tag.
Added recursive workspace support to pnpm outdated. pnpm list and pnpm ll now inspect all workspace projects by default, matching the TypeScript CLI.
Made recursive pnpm rebuild honor workspace filters with shared and dedicated lockfiles.
Made pnpm why and pnpm peers recursive by default in workspaces. Recursive peer checks now honor workspace filters, and recursive why can inspect the active project when a workspace uses dedicated lockfiles.
Added a --changeset flag to pnpm update. Set update.changeset to true in pnpm-workspace.yaml to enable this behavior by default, and use --no-changeset to override the setting for one update. After the update completes, pnpm writes a .changeset/pnpm-update-<suffix>.md file declaring a patch bump for every workspace package whose dependencies or optionalDependencies were changed by the update and a major bump when peerDependencies changed, including packages that consume an updated catalog entry via the catalog: protocol. Private packages, packages without a name, and packages listed in the ignore array of .changeset/config.json are skipped. If .changeset/config.json does not exist, a warning is printed and no changeset is generated.
Added GitHub Actions dependencies to pnpm outdated and interactive pnpm update. Non-interactive updates can include them with --include-github-actions or by setting update.githubActions to true in pnpm-workspace.yaml. Updated actions are pinned to exact commit hashes with their release tags preserved in comments.
Fixed pnpm install rewriting unrelated pnpm-lock.yaml entries after a small manifest change — for example, removing one dev dependency could bump other packages' open-range dependencies (such as jest's @types/node: '*') to their newest versions pnpm/pnpm#13193. Three resolution-reuse gaps caused still-satisfied lockfile entries to be re-resolved from the registry:
catalog: protocol were compared against the lockfile in their resolved-range form, so every catalog-managed dependency looked changed on every install, and any package depending on one was re-resolved.pnpm install, pnpm add, pnpm update, and pnpm remove now support recursive (-r) and filtered (--filter) execution in workspaces configured with one lockfile per project (sharedWorkspaceLockfile: false), instead of failing with ERR_PNPM_RECURSIVE_SHARED_LOCKFILE_UNSUPPORTED. Each selected project is installed independently against its own pnpm-lock.yaml, node_modules, and virtual store, matching pnpm.
Global commands (pnpm add -g, pnpm runtime set -g, ...) now create a missing global bin directory instead of failing with ERR_PNPM_PNPM_DIR_NOT_WRITABLE, and the universal --silent / -s shorthands for --reporter=silent (e.g. pnpm store path --silent) are supported again.
pnpm unlink now reinstalls through the selection-aware install pipeline, matching pnpm: it honors -r / --filter, installs recursively by default inside a workspace, and supports both a shared workspace lockfile and one lockfile per project (sharedWorkspaceLockfile: false). Previously it always reinstalled only the active project.
Added update and audit settings sections to pnpm-workspace.yaml, superseding the awkwardly named updateConfig, auditConfig, and top-level auditLevel settings:
update:
ignoreDeps: # was updateConfig.ignoreDependencies
- webpack
- "@babel/*"
audit:
level: high # was auditLevel
ignore: # was auditConfig.ignoreGhsas
- GHSA-xxxx-yyyy-zzzz
update.ignoreDeps lists dependency name patterns that pnpm update and pnpm outdated should skip. audit.level and audit.ignore tune pnpm audit.
The deprecated updateConfig, auditConfig, and auditLevel settings keep working until the next major version. When both a new section value and its deprecated counterpart are set, the new section takes precedence and a warning is printed. Both the TypeScript CLI and the Rust config surface (pacquet) recognize the new sections.
Added support for alias-less Git dependency adds, preserved locked Git commits during unrelated dependency changes, and reported Git package versions
Added support for alias-less Git dependency adds, preserved locked Git commits during unrelated dependency changes, and reported Git package versions in install logs.
pnpm list and pnpm why are now feature complete and behaviorally identical to the TypeScript CLI. pnpm list gained --only-projects, --find-by (finders declared in .pnpmfile.cjs), search by version range (pnpm ls "foo@^2"), subtree deduplication with [deduped] markers, peer/skipped annotations, the package-count summary, --long manifest details, resolved tarball URLs and absolute paths in --json/--parseable output, and --depth support for globally installed packages. pnpm why gained --json, --parseable, --long, --prod/--dev/--no-optional, --find-by, workspace project names in the reverse tree, dependency-field annotations, [circular]/[deduped] markers, peer-variant hashes, and the Found N versions summary.
Added support for the cleanupUnusedCatalogs setting: when enabled, pnpm add, pnpm update, and pnpm remove drop catalog entries from pnpm-workspace.yaml that no workspace project references.
The enableModulesDir: false setting is now honored: the install resolves and writes pnpm-lock.yaml but creates no node_modules directory (unless the global virtual store is enabled, in which case packages are still materialized into the store).
Command shims now set NODE_PATH the way pnpm does: under the isolated nodeLinker with a hoist pattern, each shim lists the target package's own node_modules directories followed by the hidden hoisted modules directory (node_modules/.pnpm/node_modules). The new extendNodePath: false setting turns this off.
Added the --force flag to pnpm install and pnpm add: optional dependencies whose cpu / os / libc / engines don't match the host are installed instead of skipped, and a forced install relinks packages that an earlier install already materialized #13142.
sharedWorkspaceLockfile: false is now supported by the install family #12042: a workspace install runs one dedicated install per project, each with its own pnpm-lock.yaml, node_modules, and virtual store (a custom virtualStoreDir resolves per project), and pnpm add / update / remove in a project operate on that project's own lockfile. Recursive and filtered install-family commands still require a shared lockfile.
Added PnP install materialization and fixed recovery from expired module caches and broken private lockfiles.
Fixed recovery from interrupted dependency builds in the global virtual store, and made pnpm fetch populate the virtual store without linking dependencies into projects.
Fixed workspace lifecycle ordering and bin linking across isolated and hoisted installs.
Auto-installed peer dependencies wanted by multiple packages under distinct but compatible ranges now resolve through the ranges' semver intersection (2 + ^2.2.0 install one provider matching >=2.2.0 <3.0.0-0), matching pnpm. Previously such peers were only auto-installed when every consumer declared the identical range or autoInstallPeersFromHighestMatch was enabled.
engineStrict now fails the install when an incompatible package is reached through a regular dependency edge of an installable package, even if the package is also optionally reachable — matching pnpm. Packages reachable only through optional edges or skipped parents are still skipped #13143.
Engine checks (engines.node / engines.pnpm) now match npm-semver's includePrerelease semantics exactly: a prerelease version no longer satisfies a fully specified >= bound (9.0.0-alpha.1 does not satisfy >=9.0.0), while still satisfying expanded ranges like 9, >=9, and ^9.0.0.
Fixed a rare hang where pnpm install or pnpm add could wait forever: when two tasks fetched the same tarball concurrently, the waiting task could miss the downloader's completion notification and never wake up.
Deprecated packages are reported during installation: a directly depended-on deprecated package gets an immediate warning, and deprecated subdependenc…
Completed pnpm runtime installation parity for Node.js, Deno, and Bun, including runtime failure policy, target architecture selection, and dependency runtime engines. Runtime failure overrides now preserve explicit runtime dependencies without matching engine entries.
Deprecated packages are reported during installation: a directly depended-on deprecated package gets an immediate warning, and deprecated subdependencies are summarized in a single <N> deprecated subdependencies found line. Versions matched by pnpm.allowedDeprecatedVersions are not warned about #11633.
Implemented native install-test command.
Implemented native recursive, multi, and m commands in the Rust CLI.
Added the virtualStoreOnly setting, which populates the virtual store without any post-import linking — no importer symlinks, no .bin entries, no hoisting, and no project lifecycle scripts. Combining it with enableModulesDir: false fails with ERR_PNPM_CONFIG_CONFLICT_VIRTUAL_STORE_ONLY_WITH_NO_MODULES_DIR unless enableGlobalVirtualStore is on, since the standard virtual store lives inside node_modules. A subsequent ordinary install completes the linking instead of treating the partially-populated directory as up-to-date. enableModulesDir is now read from pnpm-workspace.yaml as well.
Repeat installs now reconcile the existing node_modules the way the TypeScript CLI does: direct dependencies removed from the lockfile lose their links and bin shims, hoisted aliases of removed packages are unlinked so the next hoist pass can claim their slots, a hand-deleted package is detected and re-installed even when the lockfile is otherwise up to date, and pnpm add / pnpm remove fail with ERR_PNPM_HOIST_PATTERN_DIFF-family errors instead of silently recreating a modules directory whose layout settings drifted. Dev-only installs also no longer delete node_modules/.pnpm/lock.yaml.
pnpm install --ignore-scripts now records the builds it skipped in node_modules/.modules.yaml's pendingBuilds, and pnpm rebuild --pending runs them and clears the record instead of finding nothing to do. Both the dependencies whose build scripts were suppressed and the workspace projects whose own install scripts were suppressed are recorded and re-run, and an install that removes a package drops it from the list.
pnpm install now fails with ERR_PNPM_UNUSED_PATCH when an entry in patchedDependencies doesn't match any installed package. Set allowUnusedPatches: true in pnpm-workspace.yaml to get a warning instead, matching pnpm 11 #11633.
pnpm add no longer drops the other dependency groups from the install: adding a package with optionalDependencies no longer leaves dangling optional-dependency symlinks in the virtual store (pnpm add -g @openai/codex produced a codex bin that failed with "Missing optional dependency @openai/codex-darwin-arm64"), and a production pnpm add no longer removes the project's devDependencies from pnpm-lock.yaml and node_modules.
pnpm add with --save-dev, --save-optional, or --save-prod now moves an already-declared dependency to the target group instead of leaving a duplicate entry in its old group, matching pnpm.
pnpm add <pkg> without a --save-* flag now updates an already-declared dependency in the group it occupies (devDependencies / optionalDependencies), matching pnpm, instead of always saving it into dependencies.
pnpm install now detects a supportedArchitectures change and re-evaluates previously skipped platform-specific optional dependencies, instead of reporting the project as up to date and leaving the packages for the old architecture set in place.
Avoided optimistic repeat-install shortcuts when a lockfile contains merge conflict markers.
pnpm setup now removes leftover v10-layout shims at the top of PNPM_HOME, so pnpm self-update no longer warns about a v10 installation layout after PATH has been migrated to the v11 PNPM_HOME/bin layout. Applies to both the TypeScript CLI and pacquet.
In the TypeScript CLI, self-update also no longer treats a dangling legacy shim (one whose install target was garbage-collected) as a real v10 layout, so the warning can no longer fire on dead shim files.
Closes pnpm/pnpm#12496.
A git-hosted dependency with no host archive (an ssh, self-hosted, or git+file: repo) whose package name matches the dependency's alias now records the bare git+<repo>#<commit> reference in the lockfile's importer entry, matching pnpm's pnpm-lock.yaml output instead of prefixing it with <name>@.
A private git-hosted dependency resolved over HTTPS with an embedded auth token (git+https://<token>@github.com/owner/repo.git) is now recorded as a type: git resolution against the authenticated remote, instead of being rewritten to the host's public archive URL (a codeload.github.com tarball) that carries none of those credentials and so could not be fetched.
When a dependency's build script fails under enableGlobalVirtualStore, the global virtual store directory it was being built in is now removed for scoped packages too. Previously the cleanup resolved one directory level short of the hash directory for a scoped name, leaving a half-built directory behind that later installs would reuse.
A hoisted-linker install no longer fails with ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY when an optional dependency's snapshot is absent because it was skipped on a previous install.
Fixed patched dependencies being applied to only one copy of a package under nodeLinker: hoisted. When a version conflict kept a patched package out of the root node_modules, the hoisted layout nested a copy of it under each consumer that needed it, but only the first copy was patched — every other copy silently ran the unpatched code the patch existed to replace. The same gap applied to a reinstall served from the side-effects cache. Every copy is now patched, matching nodeLinker: isolated and pnpm's behavior.
pnpm install now announces Lockfile is up to date, resolution step is skipped whenever the headless installer runs — including installs that materialize a cold node_modules from an up-to-date lockfile and --filter subset installs — matching the TypeScript CLI. pnpm fetch prints Importing packages to virtual store on that path instead.
Conditional metadata requests send If-Modified-Since as an HTTP-date instead of the mirror's raw ISO-8601 modified value, so registries can answer 304 Not Modified instead of re-serving the full packument #13104.
Fixed two global-virtual-store correctness gaps. A failed build now discards the hash directory it was building in, so the next install re-fetches instead of reusing a half-built directory shared by every project with the same dependency graph. The removal only ever touches a slot strictly inside the store, so a crafted package name cannot make it escape. And a side-effects-cache hit no longer assumes the store slot still holds the cached build: when the slot has been re-imported pristine, the build output is materialized rather than skipped, which previously left the package without its build artifacts.
.modules.yaml now records the allowBuilds set the install ran under, matching pnpm.
Aligned large-download progress byte formatting with pnpm.
Changing --os / --cpu / --libc or supportedArchitectures between installs now re-evaluates previously skipped optional dependencies, so the platform packages for the newly selected architecture are installed instead of staying skipped.
Removing a package from allowBuilds now fails the next pnpm install under strictDepBuilds instead of reporting the project as already up to date. A build whose output is already cached in the store no longer counts as an approval #11035.
.modules.yaml now records the dependencies of a skipped optional package in skipped as well, matching pnpm: when a platform-incompatible optional package is skipped, its own dependency subtree is not materialized either.
Fixed proxy settings from the global config.yaml and command-line options in pnpm.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →