Practical guide · CI failures
npm ci: “package.json and package-lock.json are not in sync”
Your build or release job stops at the install step with this error. It means package.json asks for dependency versions that package-lock.json doesn't contain. Here's how to fix it properly, and how to spot the repository setup that keeps causing it.
Published 19 September 2026
npm error code EUSAGE
npm error `npm ci` can only install packages when your package.json and
npm error package-lock.json or npm-shrinkwrap.json are in sync. Please update
npm error your lock file with `npm install` before continuing.
npm error
npm error Invalid: lock file's axios@1.13.6 does not satisfy axios@1.20.0
npm error Missing: jscpd@5.0.14 from lock fileWhat it means
npm ci installs the lockfile exactly, or not at all
npm ci exists for reproducible installs. It deletes node_modules, installs exactly the versions recorded in package-lock.json, and never updates the lockfile. Before it starts, it checks that the lockfile still satisfies every dependency range in package.json.
Each Invalid: line is a package whose locked version is outside the range package.json now asks for. Each Missing: line is a dependency that was added to package.json but never written to the lockfile.
The fix
Regenerate the lockfile and commit it
- On a clean checkout of the failing branch, run
npm installwith the same npm major version your CI uses (npm -vin both places). - Commit the updated
package-lock.json. Check that the diff contains the packages named in the error. - Confirm locally with
npm ci, or withnpm ci --dry-run --ignore-scriptsfor a quick check without installing. - Keep
npm ciin CI. Switching CI tonpm installmakes the error go away by giving up reproducible builds.
Why it keeps coming back
Most repeat cases have two package managers
A one-off mismatch usually comes from a hand edit to package.json or a merge conflict resolved in package.json but not the lockfile. When the error keeps returning, the usual cause is structural: the repository has two lockfiles.
A typical example: day-to-day work uses Bun or pnpm, so dependency upgrades, whether by people, bots or AI coding tools, update package.json and bun.lock or pnpm-lock.yaml. But a release workflow written earlier still runs npm ci against a package-lock.json that nobody updates any more. Everything looks healthy until the scheduled release runs. Then it fails every time, often for weeks before anyone notices.
Check 1
More than one lockfile in the package? package-lock.json next to bun.lock, pnpm-lock.yaml or yarn.lock.
Check 2
Which manager does each workflow, Dockerfile and deploy config install with? Look for npm ci in jobs that don't match.
Check 3
What does package.json declare? The packageManager field, if present, is the team's stated decision.
If you find two lockfiles, choose one manager. Delete the other lockfile, declare the one you keep in package.json ("packageManager"), and make every install step use it. If Bun or pnpm is the real manager, change the release job to use it instead of npm ci.
Free check
Catch the second lockfile before it breaks a release
CrossCheck is a free, open-source check from zFinia. It flags two package managers configured for the same package, and lists every CI, Docker and Vercel install step with the manager it uses. So “CI tests with Bun but the release runs npm ci” is visible in the pull request, not on release day. It reads configuration files only and doesn't parse lockfile contents, so a hand-edited package.json in an npm-only repository is still npm ci's job to catch.
FAQ
Why does npm install work but npm ci fail?
npm install is allowed to change package-lock.json to match package.json, so it quietly repairs the mismatch on your machine. npm ci never writes the lockfile; it installs exactly what package-lock.json records and stops if that no longer satisfies package.json. That is why the error usually appears only in CI.
Should I change CI from npm ci to npm install?
No. That hides the problem and makes every CI run resolve dependencies afresh, so CI can install something nobody tested. Regenerate and commit package-lock.json instead, and keep npm ci.
Can a repository have both package-lock.json and bun.lock (or pnpm-lock.yaml or yarn.lock)?
Only if different packages deliberately use different managers. For a single package, two lockfiles means two dependency histories. Updates land in one of them, the other falls behind, and any job that installs from the stale one breaks or installs different versions.
How do I stop it coming back?
Pick one package manager, declare it with the packageManager field in package.json, make every CI, release and deploy job install with it, and delete the other lockfiles. A pull-request check such as CrossCheck flags a second lockfile and shows which manager each install step uses.