Skip to content

pacquet: peer contexts diverge from pnpm 11 on large workspaces #13334

Description

@zkochan

Note

Re-scoped 2026-07-26. The original report bundled five root causes. Three are fixed and one is now tracked separately, so this issue now covers only the peer-resolution divergence that remains. The original report is preserved verbatim below.

Root cause Status
Scoped @scope/name@file: ids never dedupe back to link: Fixed in #13374
bundledDependencies resolved as regular dependencies Fixed in #13374
Importer's own peerDependencies reaching its lockfile entry without autoInstallPeers Fixed in #13372
Local file:*.tgz deps resolve without a manifest Split out to #13379
Peer contexts diverge on large workspaces This issue

Measured against pnpm 11.17.0 on the same Astro checkout, the lockfile diff went from 306 lines (on main at the time of the report — not the 957 the original recorded on 12.0.0-alpha.21) to 157 lines, all of it now this one cause.

What remains

pacquet's peer resolution produces different peer-variant sets than pnpm on large workspaces. Two symptoms, one class:

1. Peers shadowed by a same-named dependency. When a package declares a name in both dependencies and peerDependencies, upstream drops it from dependencies — always under autoInstallPeers, and otherwise only when the name is already visible in the resolving parent's alias scope (options.parentPkgAliases in installing/deps-resolver/src/resolveDependencies.ts). pacquet models the first arm in omit_peer_shadowed_dependencies and documents the second as unmodeled. That is what drops the wrangler peer from @cloudflare/vite-plugin and the @atcute/lexicons peer from @atcute/identity, cascading into duplicate @cloudflare/vite-plugin variants and an extra @cloudflare/workers-types entry in @flue/cli's transitivePeerDependencies.

Upstream's effective behavior is "the first occurrence's parent scope decides", since both the resolved package and its children are memoized per package id. pacquet memoizes the same things but picks the owner deterministically rather than by arrival, so porting this means threading a parent-alias set through the walk without reintroducing order dependence — that is the design question.

2. Variants that should collapse but don't. @standard-community/[email protected] and @standard-community/[email protected] declare no dependencies at all, so this is not the shadowing bug. pacquet emits two extra variants whose children and resolved peers are strict subsets of the full variant, which upstream's dedupePeerDependents would collapse. pacquet's is_compatible_and_has_more_deps agrees they are collapsible; what blocks it is the collapsed_target_matches_parent guard in dedupe_peer_dependents.rs, which has no upstream counterpart and vetoes the child-edge rewrite that would orphan them.

That guard was added in #12372 to compensate for a peer-resolution divergence (#12330), so removing it in isolation would regress that. It is a symptom of the same underlying gap rather than an independent bug, which is why both symptoms belong in one issue.

Reproduction

Unchanged from the original report below, with pnpm 11.17.0 as the reference. The remaining 157-line diff is entirely @cloudflare/vite-plugin, @atcute/identity, @flue/cli and the two @standard-community packages.


Original report

Verify latest release

This reproduces with a release build of pacquet from pnpm main at 875073e596a2def295c7f68cb82e45c6bf0075bd (pnpm 12.0.0-alpha.21).

pnpm version

12.0.0-alpha.21

Which area(s) of pnpm are affected?

Dependencies resolver, Lockfile, Package manager compatibility

Link to the code that reproduces this issue

https://github.com/withastro/astro/tree/b01a6921cd8be574db2d82a6d2bbde7c7d319295

Reproduction steps

  1. Check out the Astro commit above.

  2. Temporarily remove trustPolicy: no-downgrade from pnpm-workspace.yaml. This is only to keep pnpm 11 from stopping on a newly published Netlify trust downgrade; make the same change for both runs.

  3. Delete pnpm-lock.yaml.

  4. Generate a lockfile with pnpm 11.13.1:

    pnpm install --lockfile-only
  5. Delete it and generate a lockfile with the release build of pacquet identified above:

    pnpm install --lockfile-only
  6. Compare the two lockfiles.

Describe the bug

The two resolvers produce materially different lockfiles from identical manifests. The diff is 957 lines (423 insertions and 534 deletions).

Examples:

  • pacquet adds an unrelated [email protected] peer context to many packages where pnpm 11 does not, including eslint, typescript-eslint, @flue/cli, @mdx-js/mdx, and several Vite plugins;

  • pacquet resolves [email protected] with @types/[email protected], while pnpm 11 resolves the workspace's @types/[email protected];

  • pacquet resolves @testing-library/[email protected] with [email protected], while pnpm 11 resolves [email protected];

  • four nested workspace file: dependencies are recorded as root-relative file: snapshots by pacquet but as local link: entries by pnpm 11. For example:

    # pnpm 11
    '@test/astro-client-only-pkg':
      specifier: file:./pkg
      version: link:pkg
    
    # pacquet
    '@test/astro-client-only-pkg':
      specifier: file:./pkg
      version: file:packages/astro/test/fixtures/astro-client-only/pkg

The same difference occurs for Astro's local renderer and fixture packages.

Expected behavior

Pacquet and pnpm 11 should resolve the same manifests to equivalent peer contexts, peer versions, and local workspace dependency references.

Node.js version

25.9.0

Operating system

macOS 26.5.2, arm64


Written by an agent (Codex, gpt-5).

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions