Repository navigation
[BUG] npm ci fails with "package.json and package-lock.json are out of sync" when new patch versions are published #8693
Copy link
Copy link
Closed
Labels
Bugthing that needs fixingthing that needs fixingNeeds Triageneeds review for next stepsneeds review for next steps
Description
Is there an existing issue for this?
- I have searched the existing issues
This issue exists in the latest npm version
- I am using the latest npm
Current Behavior
Current Behavior
npm ci can fail unexpectedly when a new version of a transitive dependency is published to the registry, even when both package.json and package-lock.json are unchanged and committed to the repository.
Example scenario:
- Project has package.json with
"foo": "^1.0.0" - Project has package-lock.json with
foo@1.0.5(latest at time ofnpm install) - A few days later,
foo@1.0.6is published - Running
npm cinow fails with:npm ci can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Invalid: lock file's foo@1.0.5 does not satisfy foo@1.0.6
This breaks the core promise of reproducible builds and makes CI/CD pipelines fragile.
Root Cause
The issue is in how npm ci validates the lockfile:
ci.jscallsbuildIdealTree()which re-resolves dependencies from package.json against the current registry statevalidateLockfile()then compares the newly resolved versions with the lockfile using strict equality (entry.version !== lock.version)- If a new version was published that satisfies the range in package.json, the ideal tree will contain the new version while the lockfile has the old version
- Validation fails even though the lockfile is perfectly valid
Related Issues
- Cannot build Superset 4.1.4 and 4.1.3 - npm ci fails with luxon version mismatch apache/superset#35797
- Cannot build Superset 4.1.2: npm ci raises error "Invalid: lock file's luxon@3.5.0 does not satisfy luxon@3.6.1" apache/superset#33881
Proposed Solution
Short-term: Add --trust-lockfile flag
Add a configuration option to skip buildIdealTree() and validateLockfile. Use the lockfile as the source of truth:
const trustLockfile = this.npm.config.get('ci-trust-lockfile')
if (!trustLockfile) {
// Current behavior: validate against re-resolved ideal tree
await arb.buildIdealTree()
const errors = validateLockfile(virtualInventory, arb.idealTree.inventory)
// ...
} else {
// Use lockfile directly for reproducible builds
arb.idealTree = arb.virtualTree
}Long-term: Fix validation logic
Change validate-lockfile.js to check if lockfile versions satisfy the package.json ranges rather than checking for exact version matches:
// Instead of:
if (entry.version !== lock.version) {
errors.push(`Invalid: lock file's ${lock.name}@${lock.version} does not satisfy ${entry.name}@${entry.version}`)
}
// Do:
const spec = getPackageJsonSpec(entry, lock.name)
if (spec && !semver.satisfies(lock.version, spec)) {
errors.push(`Invalid: lock file's ${lock.name}@${lock.version} does not satisfy package.json requirement ${spec}`)
}This would catch genuinely broken lockfiles while allowing valid lockfiles to work regardless of registry state.
Expected Behavior
npm ci should:
- Only fail validation if the lockfile is genuinely out of sync (e.g., lockfile has
foo@1.0.0but package.json requires^2.0.0). It should not fail simply becausefoo@1.0.1is released.
Steps To Reproduce
- Clone superset
- Checkout branch 4.1.4
cd superset-frontend- Use node 18
npm ci- Observe failure
Environment
- npm: 10.8.2
- Node.js: 18.20.8
- OS Name: Ubuntu 24.04.1
- System Model Name:
- npm config:
; node bin location = HOME/.nvm/versions/node/v18.20.8/bin/node
; node version = v18.20.8
; npm local prefix = HOME/git/superset/superset-frontend
; npm version = 10.8.2
; cwd = HOME/git/superset/superset-frontendReactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Bugthing that needs fixingthing that needs fixingNeeds Triageneeds review for next stepsneeds review for next steps
Activity
fix: add option skip-lock-validation to npm ci (npm#8693)
wraithgar commentedon Oct 23, 2025
I'm not sure this is really a problem. That example scenario is not one that anyone has opened an issue about. The bot response in apache/superset#35797 is not true. A new dependency in the registry will not cause that error to suddenly happen.
This seems to be a problem specific to how apache/superset is managing their package lock and package.json file. After some investigating the problem appeared to be that luxon wasn't properly defined in
package.json, and a dependabot update broke things when it updated other deps but not subdependencies. ref: https://github.com/apache/superset/pull/30859/filesOskar-Bonde commentedon Oct 24, 2025
@wraithgar You are right that the reason "npm ci" stopped working is probably not because a new patch of luxon was released.
Most recently,
npm cistopped working on 2025-10-18. This date does not coincide with the release of a new luxon version, or with the release of any other package that depends on luxon.The issue is not that luxon wasn't defined in
package.json. This issue as reoccurred twice, even though luxon was defined in package.json with version range (^3.5.0).Running
npm explain luxonin superset 4.1.4 today gives the following result:Despite the fact that luxon is pinned to 3.6.1 in package-lock.json.
I don't think
npm cishould break just because it thinks that an update is necessary. It must be possible to create repeatable builds using npm.Oskar-Bonde commentedon Oct 27, 2025
@wraithgar thank you for taking the time to review this issue. I think this issue has been closed prematurely. Can we continue the discussion here or should I open a new bug?
In order to make
npm ciwork again in superset 4.1.4 we have to pin the luxon version inpackage.json"dependencies": { ... - "luxon": "^3.5.0", + "luxon": "3.6.1",This to me indicates that something is wrong with
npm ci. Pinning the package versions is the role ofpackage-lock.json.I do not think
npm cishould fail to install if the lockfile is out of sync with the manifest / an update is needed.wraithgar commentedon Oct 27, 2025
This is 100% intentional and is a fail safe against a tree that got changed improperly.
If you can find a reproduction step that shows npm itself generating a package-lock that can later become broken, please submit an issue w/ those steps.