Repository navigation
pnpm 12 fails when a dependency of a platform-skipped optional package can't be resolved #16511
Description
Last pnpm version that worked
11.28.3
pnpm version
12.8.1
Code to reproduce the issue
Repro: https://github.com/everett1992/pnpm-optional-subdep-repro
repro -optional-> @repro/freebsd-only (os: [freebsd]) -> @repro/missing (404)
The project's only dependency is optional and only installs on FreeBSD. That package has a regular dependency on @repro/missing, which the registry doesn't serve. The repo includes a pnpr config with the packages already in its storage, so no publishing is needed.
git clone https://github.com/everett1992/pnpm-optional-subdep-repro
cd pnpm-optional-subdep-repro
npx -y @pnpm/pnpr@0.1.0-alpha.14 -c registry/pnpr.yaml &
cd freebsd-only
npx pnpm@11.28.3 install # OK
rm -rf node_modules pnpm-lock.yaml
npx pnpm@12.8.1 install # ERR_PNPM_FETCH_404Expected behavior
@repro/freebsd-only is skipped on this platform, so @repro/missing is never needed and the install succeeds. npm 11.19.1, pnpm 10.34.6 and pnpm 11.28.3 all succeed. pnpm 11 locks the package with no dependencies:
snapshots:
'@repro/freebsd-only@1.0.0':
optional: trueActual behavior
Error: ERR_PNPM_FETCH_404
à installing dependencies
â°ââķ Failed to resolve dependency tree: GET http://127.0.0.1:7677/
@repro%2Fmissing: Not Found - 404
A registry that returns a packument with no versions fails the same way, with ERR_PNPM_NO_VERSIONS. We hit this through sharp 0.35: @img/sharp-freebsd-wasm32 (os: [freebsd]) and @img/sharp-webcontainers-wasm32 (cpu: [wasm32]) depend on @img/sharp-wasm32, which our registry mirror doesn't serve. So projects that depend on it, including Next.js apps, fail to install behind that mirror on pnpm 12. A lockfile written by pnpm 11 keeps installing on pnpm 12 until something forces a re-resolve.
Additional information
Cause. pnpm 11 makes every dependency of a non-installable package optional during resolution, so the failure is skipped:
addResolvedPackageToGraph.ts#L52-L53computesinstallablefrom the parent and the package's own platform fields.resolveDependency.ts#L64-L66marks the children of a non-installable parent optional.
pnpm 12 has the same installable field on tree nodes, but it's always true. Its doc comment says "Always true for the npm-shaped slice pacquet currently exposes" (resolved_tree.rs#L237-L240), and the only place nodes are built passes true (children_ownership.rs#L396). So drop_failed_optional_edge only drops the failure when the edge itself is optional. Platform skipping happens after resolution, too late for this case.
This seems to contradict the pnpm 12 engineStrict docs (#13286): "A package reachable only through optional edges, or through a package that was itself skipped, is still skipped in both versions."
A related difference from npm. The repo's any-os/ project is the same shape without the os field. npm installs it: it records @repro/any-os as optional in package-lock.json and installs nothing. pnpm 10, 11 and 12 all fail, because a regular dependency of an installable optional package must resolve. pnpm 11's result for freebsd-only therefore depends on the platform: with supportedArchitectures: { os: [freebsd] } it fails too. If npm's rule (drop the subtree at the nearest optional ancestor) is preferred, fixing that would also fix this regression and give the same result on every platform.
Related: #13041 (an unresolvable direct optional dependency, fixed separately) and #13326 (the subtree of a platform-incompatible optional package is still resolved).
Node.js version
v24.14.1
Operating System
Linux
Written by an agent (Claude Code, claude-opus-5-5).
Activity
github-actions commentedon Oct 2, 2026
@everett1992, are you planning to open a pull request for this? If so, reply here and a maintainer will assign the issue to you, so nobody else starts on the same fix.
everett1992 commentedon Oct 2, 2026
I'll work on a PR
zkochan commentedon Oct 2, 2026
A fix is open in #16527.
It follows the pnpm 11 rule from the analysis above. The install layer now gives the resolver a host platform check (
os/cpu/libc,supportedArchitectures, and name inference). When an optional package fails that check, every dependency below it resolves as optional. A dependency there that 404s or has no versions is then skipped, and the package is locked without it, as pnpm 11 does.The fix covers the platform case reported here, including the
sharp0.35 subpackages. It does not change two related cases:enginesmismatch as non-installable here. This fix checks the platform only.If you already have a branch for this, please say so here.
Written by an agent (Claude Code, claude-opus-5-5).
everett1992 commentedon Oct 2, 2026
I was exploring both options (just fixing the os dependencies, and matching npm's handling of optional sub-trees), neither has finished. I'm happy with landing your fix and we can visit this separately. Should I open a discussion for that?
IMO npm's behavior is nicer. If succeeding when
foocannot install is okay, why would we fail iffoocan install, but can't satisfy it's dependencies?On the other hand I think optional dependencies are all really sloppy - I wish they were more like cargo's
featuredependenciesfix(resolver): leave out an optional dependency whose subtree cannot âĶ
zkochan commentedon Oct 2, 2026
Correction to my earlier comment: #16527 now takes the npm approach instead of the pnpm 11 one.
Copying pnpm 11 would have made the lockfile depend on the machine. A Linux run would lock
@img/sharp-freebsd-wasm32without@img/sharp-wasm32, and a FreeBSD frozen install of that lockfile would then be incomplete.With the PR, a dependency that fails to resolve anywhere below an optional dependency leaves out the nearest optional dependency above it, together with its subtree. This is the rule from your
any-os/comparison. It covers both of your repro projects and gives the same lockfile on every platform. Neithersharp-freebsd-wasm32norsharp-webcontainers-wasm32is locked when the mirror lacks@img/sharp-wasm32. If a package needs that subtree through regular dependencies only, the install still fails.Written by an agent (Claude Code, claude-opus-5-5).
fix(resolver): leave out an optional dependency whose subtree cannot âĶ
fix(deps): update dependencias no-major (#23)
chore(deps): update pnpm to v12.9.0
chore(deps): update pnpm to v12.9.0
chore(deps): update pnpm to v12.9.0
chore(deps): update pnpm to v12.9.0
chore(deps): update pnpm to v12.9.0