# npm Lockfiles Are Resolver-Version Specific
A `package-lock.json` generated by one major version of npm can be silently rejected by another major version, even at the same `lockfileVersion`. Specifically, when nested transitive dependencies have peer requirements that don't agree with their resolved versions, **older npm tolerates the inconsistency; newer npm refuses it** — or vice versa, depending on which release introduced stricter checks.
The error surfaces only at `npm ci`, not `npm install`, with messages like:
<!-- atomicity: 1 idea ✓ -->
```
npm error EUSAGE
npm error `npm ci` can only install packages when your package.json
and package-lock.json or npm-shrinkwrap.json are in sync.
npm error Missing: <package>@<version> from lock file
```
The dependency *is* in the lockfile — it's just resolved at a path the stricter resolver finds inconsistent.
## Why this is non-obvious
`lockfileVersion: 3` is supported by npm 7+, so the file format itself is portable. What's NOT portable is the **resolution algorithm** that decides which transitive copy lives at which `node_modules/` path. When `node_modules/parent/node_modules/grandparent/node_modules/dep` resolves to v1.x but `grandparent/package.json` requires `^2.x`, the lockfile records both facts and the validator decides whether that's a contradiction.
The ground truth: **whoever generates the lockfile must use the same npm major as whoever validates it**.
## When it bites
Concrete example — post-kinds-for-indieweb, 2026-05-03: dev machine on Node 25 + npm 11 ran `npm install`, locked `node_modules/@wordpress/dataviews/node_modules/@wordpress/components/node_modules/date-fns` at 3.6.0 even though that nested @wordpress/components requires `^4.1.0`. Local `npm ci` passed; CI on Node 20 + npm 10 failed every npm-touching job (Build, JS Linting, JS Unit Tests, CSS Linting, Security Scan, WordPress Plugin Check) at install with `Missing:
[email protected] from lock file`.
## The fix
Regenerate the lockfile with the same npm major that CI uses:
```bash
npx npm@10 install --package-lock-only --ignore-scripts
```
Then never let a different npm major touch the lockfile until CI is upgraded — local `npm install` on a newer npm will revert the fix on the very next run.
## Hardening
To prevent the silent revert, pin the npm version locally:
```json
// package.json
"engines": {
"node": ">=20.0.0",
"npm": ">=10.0.0 <11.0.0"
}
```
```ini
# .npmrc
engine-strict=true
```
This still doesn't catch the original divergence — only fast-forwards the discovery to dev machines. The deeper fix is matching CI's Node version locally (e.g. via `.nvmrc`).
## Generalizes to
Any project where:
- `package-lock.json` is committed
- CI uses `npm ci` (the strict, lockfile-as-source-of-truth installer)
- Developer Node versions can drift ahead of CI's
Effectively: every long-lived JS project on a maintained CI system.
## Related concepts
- The deeper principle: **a lockfile is a serialization of one resolver's decision tree**. Different resolvers reading it can legitimately disagree on whether it's valid.
- `npm ci` vs `npm install`: only `ci` enforces the consistency check. Local devs running `install` won't notice the drift.
## Links
- [[3. Resources/Permanent Notes/Zet · WordPress lacks a unified package manager]] — contrasts: WordPress lacks a unified dependency workflow, while npm shows that even a unified workflow still inherits resolver-version assumptions
- [[3. Resources/Permanent Notes/Pin Composer Platform PHP for Portable CI Lockfiles]] — extends: Composer platform pinning addresses the same hidden-environment problem by making the resolver's target runtime explicit
- [[Updating WordPress Core with Gutenberg Features]] — evidence-for: Gutenberg's proposal to regenerate the package lock when publishing packages treats resolver output as a versioned release artifact.
- [[3. Resources/Permanent Notes/General WordPress-to-WordPress Sync Is Unsolvable Because Environment Is Part of the Site]] — evidence-for: a lockfile that changes with resolver environment is a concrete case of a supposedly portable artifact retaining hidden environmental state
- [[3. Resources/MOCs/Open Source Governance MOC]] — applies-in: the MOC lists this note under Connected Permanent Notes (synthesis 2026-07-21 — run 10).