You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The registry I hit this on is publicly readable without credentials, so this reproduces anywhere:
https://packagefeedproxy.microsoft.io/npm/
More generally, the only requirement is a registry whose dist.tarball is not derivable from the registry URL — i.e. it points at a different host than the one that served the packument. Any Artifactory-style mirror or load-balanced proxy behaves this way.
Point npm at such a registry in your user-level~/.npmrc (a project-level .npmrc is not used for this code path):
With a different pnpm installed globally (e.g. npm install -g pnpm@11.15.1), run any pnpm command in that directory:
pnpm --version
pnpm tries to switch to the pinned version and fails before downloading anything.
Expected behavior
pnpm downloads pnpm@11.10.0 from the configured registry, verifies it, switches to it, and runs — as it does on 11.5.2 and earlier, and as it still does against registry.npmjs.org.
Actual behavior
[ERROR] The packageManager dependency "pnpm@11.10.0" in pnpm-lock.yaml must use a registry package path and an integrity-only resolution
(The message names pnpm-lock.yaml, but this occurs with no lockfile present — the reference is to the internal env lockfile pnpm builds for the bootstrap.)
Every pnpm command in the project fails this way, so the project is unusable until the pin is removed or pmOnFail is set to ignore.
Additional information
Cause
assertIntegrityOnlyResolution() in pnpm/src/packageManagerLockfile.ts requires the resolution for a package-manager dependency to have exactly one key, integrity:
toLockfileResolution() only drops tarball when the URL equals getNpmTarballUrl(name, version, { registry }). When a registry serves tarballs from another host, the URL can never match, so tarball is always retained and the assertion always throws.
Concrete example
The registry I hit this on is a load-balanced proxy: it serves packuments from one host and tarballs from a pool of others, and the tarball host changes between requests. Asking for the same package five times in a row:
The packument comes from packagefeedproxy.microsoft.io, so the tarball URL is not reconstructible from registry + name + version, and cannot be made so.
The tarball bytes are authentic — their SHA-512 matches npm's published dist.integrity for pnpm@11.10.0 exactly. Only the URL shape differs.
Which versions changed, and the responsible PR
pnpm
Result
9.6.0
no switch (feature not present)
9.7.0 / 10.0.0 / 11.5.2
switches successfully
11.5.3
fails with the error above
11.15.1, 11.16.0
fails
Introduced by #12296 (fix: harden package-manager bootstrap metadata, commit 822beb5), which added pnpm/src/packageManagerLockfile.ts.
Note the 10.x line is unaffected: the port to v10 (#12300) deliberately omitted the env-lockfile validation, stating "The env-lockfile validation parts of the main PR (packageManagerLockfile.ts, syncEnvLockfile, peer-suffix handling) do not apply."
Why this seems too strict
No spec requires a canonical tarball URL. npm documents tarball only as "the url of the tarball containing the payload for this package", and REGISTRY-API.md says it is "usually"<registry>/<name>/-/<name>-<version>.tgz. Rewriting it is normal — Verdaccio and Artifactory both do it.
pnpm already accepts non-derivable tarball URLs everywhere else. Per @pnpm/lockfile.utils 1100.0.6: "Restored the heuristic that preserves tarball URLs in pnpm-lock.yaml when they cannot be derived from name+version+registry … most notably GitHub Packages … and JSR." The package-manager bootstrap applies the opposite rule to the same data.
The URL here is not repository-controlled. It is resolved fresh from the trusted bootstrap registry (user/global .npmrc) and then rejected, even though pnpm just fetched it from the registry it was told to trust. The bytes are separately pinned by integrity and by the signature check added in fix(security): verify npm registry signature before spawning a package-manager binary #12292.
Possible fix
Accept a stored tarball alongside integrity for package-manager dependencies, leaving signature verification unchanged.
A narrower variant — accepting a stored URL only when its origin is among the configured/trusted registries — would keep the fetch-destination intent, but would not help registries like this one, whose tarball origin legitimately differs from its packument origin.
Others hitting this
Same error, same cause (an Artifactory mirror rewriting the tarball host), diagnosed independently in hakula139/nixos-config#120:
pnpm 11 hardened its package-manager self-install: when a repo pins packageManager […] pnpm fetches it as @pnpm/exe and asserts an integrity-only resolution. The artifactory npm mirror rewrites the tarball host to its own domain, so the resolution carries a tarball field alongside integrity and the assertion throws.
Workarounds seen in the wild: scoping only @pnpm to registry.npmjs.org, pinning a public registry in the project .npmrc, or setting pmOnFail: ignore — all of which give up either the mirror or the version pin.
Last pnpm version that worked
11.5.2
pnpm version
11.16.0
Code to reproduce the issue
Reproduction repo: https://github.com/astegmaier/playground-pnpm-package-proxy
The registry I hit this on is publicly readable without credentials, so this reproduces anywhere:
More generally, the only requirement is a registry whose
dist.tarballis not derivable from the registry URL — i.e. it points at a different host than the one that served the packument. Any Artifactory-style mirror or load-balanced proxy behaves this way.Point npm at such a registry in your user-level
~/.npmrc(a project-level.npmrcis not used for this code path):Create a
package.jsonpinning a pnpm version that differs from the one you have installed globally:{ "name": "repro", "version": "1.0.0", "private": true, "packageManager": "pnpm@11.10.0" }With a different pnpm installed globally (e.g.
npm install -g pnpm@11.15.1), run any pnpm command in that directory:pnpm tries to switch to the pinned version and fails before downloading anything.
Expected behavior
pnpm downloads
pnpm@11.10.0from the configured registry, verifies it, switches to it, and runs — as it does on11.5.2and earlier, and as it still does againstregistry.npmjs.org.Actual behavior
(The message names
pnpm-lock.yaml, but this occurs with no lockfile present — the reference is to the internal env lockfile pnpm builds for the bootstrap.)Every pnpm command in the project fails this way, so the project is unusable until the pin is removed or
pmOnFailis set toignore.Additional information
Cause
assertIntegrityOnlyResolution()inpnpm/src/packageManagerLockfile.tsrequires the resolution for a package-manager dependency to have exactly one key,integrity:toLockfileResolution()only dropstarballwhen the URL equalsgetNpmTarballUrl(name, version, { registry }). When a registry serves tarballs from another host, the URL can never match, sotarballis always retained and the assertion always throws.Concrete example
The registry I hit this on is a load-balanced proxy: it serves packuments from one host and tarballs from a pool of others, and the tarball host changes between requests. Asking for the same package five times in a row:
The
distobject it returns:{ "shasum": "43767258be015d25fe0f81e5c07919c002914d90", "tarball": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/pnpm/-/pnpm-11.10.0.tgz" }The packument comes from
packagefeedproxy.microsoft.io, so the tarball URL is not reconstructible from registry + name + version, and cannot be made so.The tarball bytes are authentic — their SHA-512 matches npm's published
dist.integrityforpnpm@11.10.0exactly. Only the URL shape differs.Which versions changed, and the responsible PR
Introduced by #12296 (
fix: harden package-manager bootstrap metadata, commit822beb5), which addedpnpm/src/packageManagerLockfile.ts.Note the 10.x line is unaffected: the port to v10 (#12300) deliberately omitted the env-lockfile validation, stating "The env-lockfile validation parts of the main PR (
packageManagerLockfile.ts,syncEnvLockfile, peer-suffix handling) do not apply."Why this seems too strict
tarballonly as "the url of the tarball containing the payload for this package", andREGISTRY-API.mdsays it is "usually"<registry>/<name>/-/<name>-<version>.tgz. Rewriting it is normal — Verdaccio and Artifactory both do it.@pnpm/lockfile.utils1100.0.6: "Restored the heuristic that preserves tarball URLs inpnpm-lock.yamlwhen they cannot be derived from name+version+registry … most notably GitHub Packages … and JSR." The package-manager bootstrap applies the opposite rule to the same data..npmrc) and then rejected, even though pnpm just fetched it from the registry it was told to trust. The bytes are separately pinned byintegrityand by the signature check added in fix(security): verify npm registry signature before spawning a package-manager binary #12292.Possible fix
Accept a stored
tarballalongsideintegrityfor package-manager dependencies, leaving signature verification unchanged.A narrower variant — accepting a stored URL only when its origin is among the configured/trusted registries — would keep the fetch-destination intent, but would not help registries like this one, whose tarball origin legitimately differs from its packument origin.
Others hitting this
Same error, same cause (an Artifactory mirror rewriting the tarball host), diagnosed independently in hakula139/nixos-config#120:
Workarounds seen in the wild: scoping only
@pnpmtoregistry.npmjs.org, pinning a public registry in the project.npmrc, or settingpmOnFail: ignore— all of which give up either the mirror or the version pin.Node.js version
24.16.0
Operating System
macOS