`pnpm dedupe` requires two passes after bumping a direct dependency (regression in 11.x)
Last pnpm version that worked
10.34.5
pnpm version
11.27.0 - the issue does not reproduce in 12.x, though.
Code to reproduce the issue
A complete reproduction repository is available at astegmaier/playground-pnpm-dedupe-instability-bug.
Create an empty directory with this package.json:
{
"name": "pnpm-dedupe-fixed-point-repro",
"version": "1.0.0",
"private": true,
"packageManager": "[email protected]",
"dependencies": {
"boxen": "5.1.2",
"chalk": "4.1.0"
}
}Then run:
# Generate a baseline lockfile containing [email protected].
pnpm install
# Bump the direct dependency without updating the lockfile first.
pnpm pkg set 'dependencies.chalk=^4.1.2'
# This should leave the lockfile fully deduplicated.
pnpm dedupe
# This unexpectedly fails.
pnpm dedupe --checkThe dependency graph is:
pnpm-dedupe-fixed-point-repro
├── chalk@^4.1.2
└── [email protected]
└── chalk@^4.1.0Both [email protected] and [email protected] satisfy boxen's range.
Expected behavior
One invocation of pnpm dedupe should reach a fixed point. Running
pnpm dedupe --check immediately afterward should succeed without finding additional
lockfile changes.
Actual behavior
The first pnpm dedupe updates the direct dependency to [email protected] but retains
[email protected] for boxen. The following check exits with code 1:
[ERR_PNPM_DEDUPE_CHECK_ISSUES] Dedupe --check found changes to the lockfile
Packages
[email protected]
└── chalk 4.1.0 → 4.1.2
- [email protected]
Run pnpm dedupe to apply the changes above.Running the same commands a second time succeeds:
pnpm dedupe
pnpm dedupe --checkThe second dedupe removes [email protected]. The package manifests did not change between the
first and second passes.
Additional information
The behavior is deterministic and still reproduces with an empty pnpm metadata cache.
The important starting condition is that the newer version is not yet represented in the lockfile. In pnpm 11, existing lockfile versions receive a much stronger preferred-version weight than a newly resolved direct version. The first pass therefore introduces the newer direct version while retaining the older compatible transitive version. Once both versions are represented in the lockfile, the second pass can converge on the higher compatible version.
| pnpm versions | Result |
|---|---|
| 8.0.0–8.15.9 | ✅ No repro |
| 9.0.0–9.15.9 | ✅ No repro |
| 10.0.0–10.34.5 | ✅ No repro |
| 11.0.0–11.27.0 | ❌ Repro |
| 12.0.0–12.4.2 | ✅ No repro |
Node.js version
24.16.0
Operating System
macOS
Source: pnpm/pnpm