why does npm ci sometimes fail with ERESOLVE even when package-lock.json is already committed and matches package.json? for example, a project works locally with npm install, the lockfile is committed, but a clean environment running npm ci fails with a peer dependency conflict. what exactly does npm ci validate differently from npm install, and what is the correct way to fix the lockfile without simply using --legacy-peer-deps or --force?
#209283
Replies: 6 comments
|
I think the important distinction here is that
So it is possible for this to happen: while a clean environment using: npm cifails with something like: A common reason is a peer dependency conflict. For example, suppose one package requires: while another package in the same dependency tree declares a peer requirement such as: The issue isn't necessarily that the lockfile is "corrupted". The problem may be that the dependency graph contains requirements that npm cannot satisfy consistently under the current resolver/configuration. What I would check firstI would start by checking the exact versions of npm and Node being used locally versus in the clean environment: node --version
npm --versionAlso check whether the project has an Then inspect the dependency tree: npm lsand, for a particular package: npm ls <package-name>For peer-dependency information, this can also be useful: npm explain <package-name>The goal is to identify which package requires which version rather than treating the The proper way to fix itOnce the conflicting packages are identified, I would fix the dependency declarations first. For example, if package A requires: but package B only supports: then the real solution is to find compatible versions of A and B, upgrade/downgrade one of them, or replace the incompatible package if necessary. After resolving the actual version conflict, regenerate the lockfile using the same Node/npm environment that will be used in CI: rm -rf node_modules package-lock.json
npm installThen verify the result from a completely clean install: rm -rf node_modules
npm ciIf I would not use One other thing worth checking is whether the lockfile was generated with special npm settings. npm's documentation notes that certain dependency-resolution flags used when creating the lockfile may also need to be used when running So, in practice, my debugging order would be:
The key point is that a committed If the exact |
|
I think the important distinction here is that
So it is possible for this to happen: while a clean environment using: npm cifails with something like: A common reason is a peer dependency conflict. For example, suppose one package requires: while another package in the same dependency tree declares a peer requirement such as: The issue isn't necessarily that the lockfile is "corrupted". The problem may be that the dependency graph contains requirements that npm cannot satisfy consistently under the current resolver/configuration. What I would check firstI would start by checking the exact versions of npm and Node being used locally versus in the clean environment: node --version
npm --versionAlso check whether the project has an Then inspect the dependency tree: npm lsand, for a particular package: npm ls <package-name>For peer-dependency information, this can also be useful: npm explain <package-name>The goal is to identify which package requires which version rather than treating the The proper way to fix itOnce the conflicting packages are identified, I would fix the dependency declarations first. For example, if package A requires: but package B only supports: then the real solution is to find compatible versions of A and B, upgrade/downgrade one of them, or replace the incompatible package if necessary. After resolving the actual version conflict, regenerate the lockfile using the same Node/npm environment that will be used in CI: rm -rf node_modules package-lock.json
npm installThen verify the result from a completely clean install: rm -rf node_modules
npm ciIf I would not use One other thing worth checking is whether the lockfile was generated with special npm settings. npm's documentation notes that certain dependency-resolution flags used when creating the lockfile may also need to be used when running So, in practice, my debugging order would be:
The key point is that a committed If the exact |
|
Hi @Ram-0777, The fundamental reason this happens lies in how npm's dependency engine ( Why
|
|
The thing I'd check first: is If it says To fix the real conflict, read the ERESOLVE block, it names the culprit, something like Upgrade it (or align the other side), then rebuild the lock with the flag off:
|
|
The difference is that npm install is allowed to resolve conflicts on the fly and update the lockfile as it goes, while npm ci treats the lockfile as gospel and fails hard if reality doesn't match it. So if your lockfile was generated in an environment where --legacy-peer-deps was set in your .npmrc, or on a different npm version that was more lenient about peer conflicts, the lockfile can contain a tree that a stricter environment refuses to install from. To actually find the conflict rather than suppress it, run npm install --dry-run locally without any legacy flags and let it surface the ERESOLVE. npm will tell you exactly which package is requesting a peer that conflicts with what's installed. From there you're looking for one of three situations — a package that hasn't updated its peer dep declarations to match the version you're actually using, two packages that need incompatible versions of the same peer, or a transitive conflict where something nested is pulling in a version that fights with a top-level dep. Once you've identified the actual conflict the clean fix is usually pinning the problematic package to a version whose peer dep range is compatible with everything else, or checking whether a newer version of it has relaxed the peer requirements. After that delete node_modules and package-lock.json, run a fresh npm install without any flags, verify it resolves cleanly, and commit the regenerated lockfile. That lockfile will then pass npm ci in a clean environment because the tree it describes is genuinely valid. |
|
Hi @Ram-0777 , A useful way to troubleshoot this is to treat it as a dependency compatibility vs. environment consistency problem. One important distinction: I'd recommend this diagnostic sequence: 1. Compare npm configurations Run these locally and in CI: node --version
npm --version
npm config get legacy-peer-deps
npm config get strict-peer-deps
npm config get install-strategyAlso check whether a user-level 2. Identify the actual conflict Inspect the complete Then check the relevant package: npm explain <package-name>
npm view <package-name>@<version> peerDependencies3. Fix compatibility before regenerating the lockfile Choose dependency versions with compatible peer requirements. If a package only supports React 18, for example, installing React 19 will not become valid simply by regenerating 4. Verify reproducibility After fixing the dependency declarations, use the same npm version and resolution settings as CI: npm install --package-lock-only
npm ciIf I would avoid deleting the lockfile as the first troubleshooting step because doing so can introduce unrelated dependency upgrades and make the original conflict harder to diagnose. The key takeaway: A committed lockfile provides reproducibility only when the dependency declarations, npm configuration, and relevant toolchain behavior are compatible. If you can share the exact |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
I’m trying to understand something about npm dependency resolution.
I have a project where npm install works normally, but running npm ci in a clean environment can fail with an ERESOLVE peer dependency conflict, even though package.json and package-lock.json are both committed.
What exactly does npm ci validate differently from npm install in this situation?
Also, what would be the proper way to fix the dependency tree and regenerate the lockfile? I’d prefer to understand and resolve the actual conflict rather than using --legacy-peer-deps or --force.
Thanks.
All reactions