Skip to content

[BUG] npm ci fails with "package.json and package-lock.json are out of sync" when new patch versions are published #8693

Closed

Description

@Oskar-Bonde

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:

  1. Project has package.json with "foo": "^1.0.0"
  2. Project has package-lock.json with foo@1.0.5 (latest at time of npm install)
  3. A few days later, foo@1.0.6 is published
  4. Running npm ci now 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:

  1. ci.js calls buildIdealTree() which re-resolves dependencies from package.json against the current registry state
  2. validateLockfile() then compares the newly resolved versions with the lockfile using strict equality (entry.version !== lock.version)
  3. 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
  4. Validation fails even though the lockfile is perfectly valid

Related Issues

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.0 but package.json requires ^2.0.0). It should not fail simply because foo@1.0.1 is released.

Steps To Reproduce

  1. Clone superset
  2. Checkout branch 4.1.4
  3. cd superset-frontend
  4. Use node 18
  5. npm ci
  6. 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-frontend

Activity

  1. wraithgar commented on Oct 23, 2025

    @wraithgar
    Contributor

    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/files

  2. Oskar-Bonde commented on Oct 24, 2025

    @Oskar-Bonde
    Author

    @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 ci stopped 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).

    • 2025-06-23: Issue
      • Error: "lock file's luxon@3.5.0 does not satisfy luxon@3.6.1"
      • package.json: "luxon": "^3.5.0"
    • 2025-10-18: Issue
      • Error: "lock file's luxon@3.6.1 does not satisfy luxon@3.7.2"
      • package.json: "luxon": "^3.5.0"

    Running npm explain luxon in superset 4.1.4 today gives the following result:

    luxon@3.7.2 peer
    node_modules/luxon
      peerOptional luxon@">= 3.x" from rc-picker@4.5.0
      node_modules/rc-picker
        rc-picker@"~4.5.0" from antd-v5@5.18.3
        node_modules/antd-v5
          antd-v5@"npm:antd@^5.18.0" from the root project
      luxon@"^3.5.0" from the root project
    

    Despite the fact that luxon is pinned to 3.6.1 in package-lock.json.

    I don't think npm ci should break just because it thinks that an update is necessary. It must be possible to create repeatable builds using npm.

  3. Oskar-Bonde commented on Oct 27, 2025

    @Oskar-Bonde
    Author

    @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 ci work 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 of package-lock.json.
    I do not think npm ci should fail to install if the lockfile is out of sync with the manifest / an update is needed.

  4. wraithgar commented on Oct 27, 2025

    @wraithgar
    Contributor

    I do not think npm ci should fail to install if the lockfile is out of sync with the manifest / an update is needed.

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Bugthing that needs fixingNeeds Triageneeds review for next steps

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions