Skip to content

pnpm 12 fails when a dependency of a platform-skipped optional package can't be resolved #16511

Closed

Description

@everett1992

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_404

Expected 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: true

Actual 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:

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

  1. github-actions commented on Oct 2, 2026

    @github-actions
    Contributor

    @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.

  2. everett1992 commented on Oct 2, 2026

    @everett1992
    ContributorAuthor

    I'll work on a PR

  3. self-assigned this
    on Oct 2, 2026
  4. zkochan commented on Oct 2, 2026

    @zkochan
    Member

    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 sharp 0.35 subpackages. It does not change two related cases:

    • pnpm 11 also treats an engines mismatch as non-installable here. This fix checks the platform only.
    • The npm difference you described (an installable optional package with an unresolvable regular dependency) still fails, as it does in every pnpm version.

    If you already have a branch for this, please say so here.


    Written by an agent (Claude Code, claude-opus-5-5).

  5. everett1992 commented on Oct 2, 2026

    @everett1992
    ContributorAuthor

    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 foo cannot install is okay, why would we fail if foo can 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 feature dependencies

  6. zkochan commented on Oct 2, 2026

    @zkochan
    Member

    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-wasm32 without @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. Neither sharp-freebsd-wasm32 nor sharp-webcontainers-wasm32 is 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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions