Repository navigation
[BUG] npm ci re-resolves workspace deps from the registry in v11.14.x, fails on lockfiles v11.13.x accepts #9357
Description
Is there an existing issue for this?
- I have searched the existing issues
This issue exists in the latest npm version
- I am using the latest npm
Current Behavior
npm ci in v11.14.0+ rejects a lockfile that v11.13.0 accepts. The verbose log shows npm ci fetching the packument for a workspace dep, picking the latest version that matches the workspace's ^ range, calling placeDep ROOT … REPLACE, and then erroring because the replacement doesn't match the (perfectly valid) version in the lockfile.
npm error code EUSAGE
npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.
npm error
npm error Invalid: lock file's @types/node@20.19.39 does not satisfy @types/node@20.19.41
Verbose trace (key lines):
silly fetch manifest @types/node@^20.19.39
silly placeDep ROOT @types/node@20.19.41 REPLACE for: <workspace-name>@<version> want: ^20.19.39
@types/node@20.19.39 satisfies ^20.19.39, so npm ci should never need to talk to the registry to verify that — but in v11.14.0+ it does, and then errors against its own re-resolution.
This breaks npm ci whenever a workspace's ^ range has had a newer patch published since the lockfile was generated, which is the steady state for any long-lived monorepo.
Expected Behavior
npm ci's contract: install exactly what the lockfile says; only fail if the lockfile cannot satisfy package.json. A lockfile that resolves 20.19.39 against a workspace's ^20.19.39 range satisfies that range — npm ci must not consult the registry, and must not "upgrade" the resolution.
This is what v11.13.0 does on the same tree.
Bisect
Tested against the same monorepo lockfile (@types/node@20.19.39 in lockfile, ^20.19.39 in workspace package.json, 20.19.41 available on registry):
| npm version / commit | Result |
|---|---|
npm@11.12.1 |
✅ passes |
npm@11.13.0 (524590f) |
✅ passes |
e7805c3 fix(arborist): propagate overrides through Link nodes to targets |
❌ fails — first failing commit |
npm@11.14.0 (409a717) |
❌ fails |
npm@11.14.1 (0fc09e8) |
❌ fails |
local HEAD on latest (post-11.14.1) |
❌ fails |
The regression is introduced by e7805c3 (PR #9198). That commit:
- Removes the post-
loadVirtual#applyRootOverridesToWorkspacesreconciliation pass inworkspaces/arborist/lib/arborist/build-ideal-tree.js. - Adds
loadOverrides: trueto the rootNodeconstructed by#rootNodeFromPackageso the root'sOverrideSetis built up at construction time. - Adds
Link.recalculateOutEdgesOverridesinworkspaces/arborist/lib/link.jsto forward overrides from a Link to its target node.
After this change, override propagation runs through every workspace target's edgesOut, calling edge.reload(true) on each. For the affected workspace dep (@types/node here, but also @wordpress/scripts via a file: workspace spec in the same trace), the reload appears to invalidate an edge that the lockfile already satisfied. That edge is then collected by #problemEdges(node) and sent through #fetchManifest → npm-pick-manifest, which returns the latest version matching the ^ range (20.19.41), and placeDep calls REPLACE at the root. validateLockfile() then rejects the resulting ideal tree against the (still 20.19.39) virtual tree.
Confirmed reverting e7805c3 makes the bug disappear (parent commit 524590fea passes the same lockfile that e7805c3a4 rejects).
Steps To Reproduce
The bug fires reliably against a real-world npm-workspaces monorepo with:
- root
package.jsondeclaringoverrides(e.g. forreact/react-domvia$reactself-reference syntax), - a workspace whose
package.jsondeclares a dep with a^range (e.g."@types/node": "^20.19.39"), - a
package-lock.jsonthat resolves that dep to the lower bound of the range (20.19.39), - the registry currently publishing a newer matching patch (
20.19.41).
Reproduction commands against such a tree:
# 1. Confirm the lockfile is internally consistent
node -e "const l = require('./package-lock.json'); console.log(l.packages['node_modules/@types/node'].version)"
# -> 20.19.39
# 2. v11.13.0 accepts it
rm -rf node_modules && npx -y npm@11.13.0 ci --dry-run --ignore-scripts
# -> exits 0
# 3. v11.14.1 rejects it
rm -rf node_modules && npx -y npm@11.14.1 ci --dry-run --ignore-scripts
# -> EUSAGE: Invalid: lock file's @types/node@20.19.39 does not satisfy @types/node@20.19.41A fully self-contained minimal repro (without the surrounding tree) has not yet reduced — the bug needs the override-propagation cascade to reach a workspace target whose recalculateOutEdgesOverrides invalidates a previously-valid edge, and the conditions for that invalidation in a small synthetic tree are still being narrowed. Reproducing under the bisect against any monorepo with the shape above is sufficient evidence of the regression, since the failing/passing bracket isolates a single commit.
Where it was noticed
Linked-strategy exploration in WordPress/gutenberg. The first failing CI run is WordPress/gutenberg actions/runs/25867266895/job/76012604708. Multiple Compressed Size and Run performance tests jobs were broken on every PR until the lockfile was force-bumped via npm install.
Environment
- npm:
11.14.0and11.14.1(andlatestHEAD) — fail.11.12.1and11.13.0— pass. - Node.js:
20.20.2(which ships npm11.14.1) and24.15.0(withnpx -y npm@<ver>) — both reproduce. - OS: macOS (Darwin 25.5.0). CI: Ubuntu via
actions/setup-node@v6(also reproduces). - npm config (representative
.npmrcfrom the affected repo):The bug also reproduces withlegacy-peer-deps = trueprefer-dedupe,lockfile-version = 3,min-release-age = 1, andinstall-strategy = linkedset or unset — none of those are required to trigger it.legacy-peer-deps = trueis needed only to bypass an unrelatedERESOLVEpeer error in the affected repo.
References
- PR that introduced the regression: #9198 —
fix(arborist): propagate overrides through Link nodes to targets(commit e7805c3 onrelease/v11, originally bc32d94 onlatest). - Affected real-world CI run: WordPress/gutenberg actions/runs/25867266895/job/76012604708.
Activity
fix(arborist): forward transitive overrides through linked store links (
fix(arborist): forward transitive overrides through linked store links (
의존성: sharp 를 0.35.5 로 올려 high 권고(CVE-2026-96889)를 막는다
Override vite to 8.3.3+ under @angular/build (#203)