npm ci "can only install with an existing package-lock.json in sync"
npm ci installs strictly from the lockfile and refuses to run if package.json and package-lock.json disagree. Unlike npm install, it never edits the lockfile to reconcile them.
What this error means
npm ci aborts immediately - before fetching anything - complaining that package.json and package-lock.json are out of sync, and usually lists which dependencies are missing from or extra in the lock. Running it again does not help; the inputs are genuinely mismatched.
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 Missing: left-pad@1.3.0 from lock fileDiagnose it: reproduce the CI install locally
Install failures are usually environment drift rather than a broken lockfile: a different package-manager major, a different Node version, or a cache that is being restored from a run with different inputs. Reproduce the CI conditions before changing the lockfile, because regenerating it hides the real cause.
# match the runner exactly, then install from a clean slate
node --version && npm --version
rm -rf node_modules
npm ci --foreground-scripts
# if that succeeds locally but fails in CI, the difference is the cache
# or the package-manager version, not your lockfileCommon causes
package.json was edited without regenerating the lockfile
Someone bumped a version or added a dependency in package.json but did not run npm install to update package-lock.json, so the two files describe different trees.
The lockfile was not committed
A dependency change landed without committing the updated package-lock.json, so CI checks out a stale lock that no longer matches package.json.
A merge left the lockfile half-updated
Resolving a merge conflict in package.json but taking one side of package-lock.json can produce a lock that satisfies neither branch.
How to fix it
Regenerate and commit the lockfile
Run a normal install locally to bring the lockfile back in sync, then commit it.
npm install
git add package-lock.json
git commit -m "chore: sync package-lock.json"Confirm CI is using npm ci deliberately
- Keep
npm ciin CI - it is the correct strict, reproducible install. - Do not switch CI to
npm installto "fix" this; that hides lockfile drift instead of resolving it. - Add a check (e.g.
npm install --package-lock-only+git diff --exit-code package-lock.json) to catch drift in PRs.
Verify the fix survives a cold cache
A green run immediately after a fix often proves nothing, because it restored a cache written before the change. Force a cold install once to confirm the fix is real.
# temporarily bust the cache key to prove the fix on a cold runner
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: package-lock.json
# then bump this suffix once, run, and remove it
# key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}-v2How to prevent it
- Always commit package-lock.json alongside any package.json change.
- Fail PRs that change package.json without an in-sync lockfile.
- Resolve lockfile merge conflicts by regenerating, not hand-merging.