Fix npm ci lockfile errors without hiding the drift.
When a clean CI install fails, the useful clue is usually above the final exit code: package-lock.json is missing, out of sync, generated with different flags, or being read from the wrong directory.
Short version: keep npm ci, align the local Node/npm configuration with CI, regenerate the lockfile intentionally, review the diff, commit it, then reproduce the clean install.
Start with the first actionable line.
npm ci is deliberately strict. The npm 12 documentation says it requires an existing package-lock.json, exits instead of updating a lockfile that disagrees with package.json, removes an existing node_modules, and does not rewrite dependency manifests. That makes the first precise mismatch more useful than the final generic failure.
| Observed failure | Most likely check |
|---|---|
| No lockfile found | For npm 12, confirm package-lock.json is committed in the directory where the command runs. Older npm releases can have different accepted inputs, so verify the CI version before applying this guide. |
| Lockfile does not satisfy package.json | Regenerate the lock using the intended npm version, review both manifests, and commit the resulting lockfile change. |
| Peer-dependency or install-layout mismatch | Check whether the lock was created with flags such as --legacy-peer-deps or --install-links. CI needs the same project-level configuration. |
| Works locally, fails only in CI | Print and compare node --version, npm --version, working directory, and the committed lockfile—not a developer's incremental node_modules. |
| Monorepo uses the wrong lock | Align the step's working-directory and any cache dependency path with the package that owns the relevant lockfile. |
A focused repair sequence.
Run this in a reviewed branch and in the package directory that CI is meant to build:
node --version
npm --version
git status --short
# Regenerate dependency metadata intentionally.
npm install
# Inspect every manifest/config change before committing.
git diff -- package.json package-lock.json .npmrc
# Reproduce the clean CI behavior.
npm ci
If the original lock was produced with tree-shaping flags, npm recommends providing the same flags to npm ci. A committed project-level .npmrc is often clearer than relying on an unrecorded developer-machine setting.
Keep the workflow explicit.
GitHub's Node.js workflow guide uses checkout, a declared Node setup step, and npm ci. Using the repository's version file avoids silently testing a different runtime locally and in CI:
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- run: npm test
Pin action references according to your repository's dependency policy. Caching can improve speed, but a cache does not repair a disagreeing package.json and lockfile; GitHub documents the cache as a restore/save mechanism keyed by dependency metadata.
Three tempting non-fixes.
- Replacing
npm ciwithnpm installin CI. That may update the lock relationship instead of exposing it, weakening the reproducibility check. - Deleting the lockfile. This discards the exact dependency tree rather than explaining why it drifted.
- Clearing caches first. A stale cache can cause other symptoms, but npm's manifest mismatch is a source/configuration problem. Fix the first actionable error before broad cleanup.
Boundary: a clean local npm ci verifies the manifest relationship in that environment. It does not guarantee registry availability, credentials, platform-specific scripts, tests, or third-party services on a hosted runner.
Primary references.
- npm 12 CLI: npm ci
- GitHub Docs: Building and testing Node.js
- GitHub Docs: Dependency caching reference
Still stuck on one workflow?
CI Rescue provides a focused diagnosis, reviewable patch, and verification report for one failing GitHub Actions job path. Public intake must be sanitized; no credentials or production access are accepted.
Request a $49 rescue →