How to check if package lock is in sync
Run npm ci --dry-run. It exits 0 when package.json and package-lock.json agree, and exits 1 with code EUSAGE and a line naming the package that is missing or invalid when they do not. The second signal is git diff --exit-code package-lock.json after an npm install.
Why check this
This is the first step of any CI job that installs dependencies, and the check a reviewer runs on a pull request that edits package.json. A lockfile that lags behind package.json means the build agent resolves versions at install time instead of reading them, so two runs of the same commit can install two different trees.
The failure it catches is a merge that adds a dependency to package.json and forgets the lockfile. Local work continues, because npm install repairs the lockfile in place and says nothing about it. The CI job that runs npm ci stops the release instead.
Prerequisites
- npm 7 or later, for lockfile version 2 or 3. The npm ci documentation states the sync requirement.
- git, with
package-lock.jsoncommitted andnode_modulesignored. - To reproduce the outputs, create a directory, put this in
package.json, add a.gitignoreholdingnode_modules/, runnpm install, and commit all three files.
{
"name": "lock-demo",
"version": "1.0.0",
"private": true,
"dependencies": {
"ms": "2.1.3"
}
}
Steps
- Step 1.
Establish the baseline on the untouched project.
npm ci --dry-run; echo "exit=$?"up to date in 688ms exit=0--dry-runresolves and compares without deletingnode_modules, so this is safe on a working tree. Exit 0 is the whole answer; the wording above it varies with what is already installed. - Step 2.
Add a dependency to
package.jsononly, leaving the lockfile untouched, then run the same command.npm ci --dry-run; echo "exit=$?"npm error code EUSAGE npm error 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 npm error Missing: dotenv@17.4.2 from lock file npm error npm error Clean install a project … exit=1The line that carries the finding is
Missing: dotenv@17.4.2 from lock file. It names the package and the version npm would have resolved. Everything fromClean install a projecton is the usage text. - Step 3.
Run
npm installon the same drifted project and read every line it prints.npm install; echo "exit=$?"added 1 package, and audited 3 packages in 1s 1 package is looking for funding run `npm fund` for details found 0 vulnerabilities exit=0It rewrote
package-lock.jsonand no line says so. The command reports an install, a funding note and a clean audit, then exits 0. This is why drift survives local development and appears only in CI. - Step 4.
Ask git what that install changed. On a pull request this is the question: did the author commit the rewrite?
git diff --exit-code --stat -- package-lock.json; echo "exit=$?"warning: in the working copy of 'package-lock.json', LF will be replaced by CRLF the next time Git touches it package-lock.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) exit=1The warning line is a Windows line-ending notice from git and not part of the finding. Exit 1 means the working tree lockfile differs from the committed one, so the branch is not reproducible until those 13 lines are committed.
- Step 5.
Widen a range instead of adding a package, and run the sync check again.
npm ci --dry-run; echo "exit=$?"up to date in 410ms 1 package is looking for funding run `npm fund` for details exit=0package.jsonnow declaresmsas^2.0.0while the lockfile still records that dependency as2.1.3. The two strings differ and the pair is reported as in sync, because 2.1.3 satisfies the range. - Step 6.
Narrow the same range past the locked version, to see the other failure wording.
npm ci --dry-runnpm error code EUSAGE npm error 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 npm error Invalid: lock file's ms@2.1.3 does not satisfy ms@2.0.0 npm error npm error Clean install a project …Invalidrather thanMissing, with both versions named. A pin edited down to2.0.0while the lockfile holds 2.1.3 produces this, and so does a range that was repointed at a different major.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| Exit 0 from npm ci --dry-run | Every range in package.json is satisfied by the lockfile | Nothing. The two files may still differ textually, which is allowed. |
| Missing: <pkg>@<version> from lock file | package.json declares a dependency the lockfile never resolved | Run npm install and commit the lockfile in the same change. |
| Invalid: lock file's ms@2.1.3 does not satisfy ms@2.0.0 | The range was narrowed or repointed past the locked version | Same fix, and confirm the narrowing was deliberate. |
| npm error code EUSAGE with no Missing or Invalid line | The lockfile is absent or an unsupported version | Read the lockfileVersion field. An npm 6 lockfile needs an npm install under npm 7 or later. |
| Exit 1 from git diff --exit-code | An install rewrote the lockfile and it was not committed | Commit it. A lockfile regenerated on every agent is not a lockfile. |
Common mistakes
What to check next
- How to check outdated npm packages: what the lockfile is holding you at, once you know it is authoritative.
- How to check npm packages for vulnerabilities: the audit reads the lockfile, so a stale one audits a tree nobody installs.
- How to check installed npm package version: confirms the tree on disk matches the lockfile you verified.
- How to check if a build is reproducible: the property this check exists to protect.
- Pull request checklist: where the dry run sits among the other gates on a dependency change.
FAQ
What is the difference between npm install and npm ci?
npm install resolves ranges and may write a new lockfile. npm ci deletes node_modules, installs exactly what the lockfile records, and refuses to start when package.json declares something the lockfile does not satisfy. One converges on a tree, the other reproduces one.
Can I check sync without touching node_modules?
Yes. npm ci --dry-run performs the comparison and stops before any file is removed or written. Steps 1, 2, 5 and 6 here were captured that way, on a project whose node_modules stayed in place throughout.
Does a lockfile diff always mean a problem?
No. npm rewrites resolved URLs and integrity metadata when the registry or the npm version changes, and that produces a diff with no dependency change in it. The signal is the exit code of npm ci --dry-run, not the size of the diff.
Which npm versions produce which lockfile?
npm 7 and later write lockfileVersion 2 or 3. The demo project here produced version 3 under npm 10.9.8. A lockfile written by npm 6 does not satisfy npm ci under npm 7 or later until an install regenerates it.
Why does npm name a version that is not in my package.json?
Missing: dotenv@17.4.2 is the version npm resolved from the range while comparing. It is a report of what the install would have picked, not something the project declared.
Verified
Verified by Maks Vernynpm 10.9.8Node.js 22.23.2git 2.41.0.windows.2
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
intermediate7 minpublished updated Maks Verny