# 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).