npm install and npm ci look interchangeable until the day a lockfile and package.json disagree. Then npm install quietly rewrites the lockfile and npm ci stops. With npm 12.2.0 current as of its 30 September 2026 release, that behavior is documented and stable, and it is the one you want in CI.
What's affected
Any pipeline that installs with npm install. It can resolve newer versions than the ones you reviewed and write them into the lockfile during the build, so what ships is not what was committed.
Per the npm docs, npm ci is different in these ways:
- It requires an existing
package-lock.jsonornpm-shrinkwrap.json. - If the lock does not match package.json, it "will exit with an error".
- It removes any existing node_modules first.
- It never writes to package.json or the lockfile.
What to do now
- Reproduce the failure. Add a dependency to package.json by hand without updating the lock:
npm pkg set dependencies.left-pad=1.3.0
npm ci
We got:
npm error code EUSAGE
npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.
npm error Missing: left-pad@1.3.0 from lock file
- Switch CI to
npm ci. Nevernpm install. - When the error appears, fix it on a developer machine with
npm install, review the lockfile diff and commit it. - Pass the same flags to
npm cias the ones used to build the lock. The docs warn that tree-shaping flags likelegacy-peer-depsmust match, and suggest committing an.npmrc. - Consider
npm ci --ignore-scriptsso install scripts do not run during the build. Run any scripts you trust explicitly afterward.
Try it yourself
Take one of your pipelines, find the install line, and check whether it says npm ci. Then see whether the build also checks the lockfile is committed.
Click through it at your own pace, or open it full screen.
Quick check
What does npm ci do if node_modules already exists?
It deletes it and installs a clean tree from the lockfile.