Skip to content

[BUG] npm ci fails because npm install produces an out-of-sync lockfile (regression since v7.0.9) #8726

Closed

Description

@xLexip

This issue exists in the latest npm version.

Summary

npm ci intermittently refuses to install because the lockfile generated by a prior npm install is not considered “in‑sync”. Rerunning npm install once (or more) “fixes” the lockfile, after which npm ci succeeds. This undermines deterministic installs and breaks CI for projects that should be reproducible.

This is a direct follow‑up to #6787, opened 2023‑09 and labeled Priority 1. It remains open (and dead) more than two years later.
The problem persists even in the latest version v11 of npm.

Maintainer attention: the current assignee of the old issue has not responded on the thread for a long time. Multiple users have asked for reassignment. Please consider re‑triaging and reassigning so this Priority 1 bug can move forward.


Why this matters

  • Breaks reproducible builds: older commits cannot be rebuilt reliably without mutating the lockfile.
  • Causes flaky CI, especially when a dependency publishes a new compatible version between runs.
  • Affects automation (e.g., Renovate) and monorepos/workspaces where lock determinism is essential.

Regression History / Technical Context

Community investigation in #6787 points to behavior changes beginning in v7.0.9 (first release where npm ci ignored locked versions), and further changes in 8.6.0, both touching installer logic. Reverting the implicated changes reportedly restores expected ci semantics up through later versions. Relevant commits discussed:

  • v7.0.9 change: 0e58e6f (first observed where npm ci may install newer than locked)
  • v8.6.0 change: bd96ae4 (tightens checks, still leads to ci failure)

Known Workarounds (insufficient)

  • Run npm install twice before npm ci.
  • Add specific overrides to coerce versions?
  • Pin semver ranges more strictly than desired.
  • Switch to an alternative package manager (e.g., pnpm) to regain determinism.

These are not acceptable long‑term substitutes for a correct npm ci.


Request

  • Re‑triage and reassign this long‑standing Priority 1 issue.
  • Provide guidance on the intended ci semantics vs. semver ranges when a lockfile is present.
  • If feasible, add an integration test that:
    1. generates a lock at t0,
    2. publishes a new minor that satisfies the range,
    3. verifies npm ci at t1 installs exactly the locked versions without error.

I’m happy to help validate patches on affected projects and CI runners.

Old issue for reference: #6787


Possible origin of the error

A deeper investigation was made here: #6787 (comment)


Expected Behavior

  • npm install should produce a lockfile that is immediately considered “in‑sync” by npm ci.
  • npm ci must install exactly the versions in package-lock.json, regardless of newer registry versions that satisfy semver ranges in package.json.

Actual Behavior

  • After the first npm install, npm ci errors with an “out of sync” message.
  • A second npm install mutates the lockfile, after which npm ci succeeds.

Steps to Reproduce (minimal)

  1. Create package.json:

    {
      "name": "testcase",
      "version": "1.0.0",
      "devDependencies": {
        "addons-linter": "6.13.0",
        "htmlhint": "1.1.4"
      }
    }
  2. Run:

    npm install
    npm ci

    You’ll see an error similar to:

    npm ERR! `npm ci` can only install packages when your package.json and package-lock.json are in sync.
    npm ERR! Invalid: lock file's <pkg>@<x.y.z> does not satisfy <pkg>@<a.b.c>
    
  3. Run npm install again.

  4. Run npm ci again → now it succeeds.

A ready‑made repro (from the original report) also exposes the issue:
https://github.com/npm/cli/files/12568428/testcase.zip


Environments where this has been observed

  • Multiple npm majors: 9.x, 10.x, 11.x.
  • Node versions vary; the behavior tracks npm CLI, not Node runtime.
  • Cross‑version scenarios (e.g., lockfile generated on npm 11.x, CI runs npm 10.x) also trigger mismatches.
    (These observations consolidate multiple user reports across the original thread and related issues. See [BUG] npm install creates unsync package-lock.json #6787)

Activity

  1. added
    Bugthing that needs fixing
    Needs Triageneeds review for next steps
    on Nov 7, 2025
  2. changed the title [-][BUG] `npm ci` fails because `npm install` produces an out-of-sync lockfile (regression since v7.0.9) — follow-up to #6787[/-] [+][BUG] `npm ci` fails because `npm install` produces an out-of-sync lockfile (regression since v7.0.9)[/+] on Nov 7, 2025
  3. ozz-oss commented on Nov 28, 2025

    @ozz-oss
  4. danshome commented on Nov 28, 2025

    @danshome

    I’m seeing this same behavior on the latest npm release.

    A single npm install appears to complete successfully, but the resulting package-lock.json is not actually in sync with package.json. Running npm ci immediately afterward fails with “package.json and package-lock.json are out of sync” error and shows several missing/invalid entries.

    If I run npm install a second time—without touching any project files—npm reports additional package additions/removals/changes. After that second install, npm ci succeeds.

    This creates a real problem in CI because developers often run npm install once, commit the updated lockfile, and the CI pipeline (which uses npm ci) fails due to the inconsistent lockfile produced by the first install.

    The key symptom is:

    • npm install (first run): completes, but produces a lockfile that npm ci rejects
    • npm install (second run): makes further unexpected changes
    • npm ci: then works

    I can reproduce this every time with the following sequence:

    # start from a clean state
    $ rm -rf node_modules package-lock.json
    
    $ npm install
    npm WARN deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
    npm WARN deprecated @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead
    npm WARN deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
    npm WARN deprecated abab@2.0.6: Use your platform's native atob() and btoa() methods instead
    npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
    npm WARN deprecated @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
    npm WARN deprecated domexception@4.0.0: Use your platform's native DOMException instead
    npm WARN deprecated critters@0.0.24: Ownership of Critters has moved to the Nuxt team, who will be maintaining the project going forward. If you'd like to keep using Critters, please switch to the actively-maintained fork at https://github.com/danielroe/beasties
    npm WARN deprecated eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
    
    added 1674 packages in 33s
    
    297 packages are looking for funding
      run `npm fund` for details
    
    $ npm ci
    npm ERR! code EUSAGE
    npm ERR!
    npm ERR! `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 ERR!
    npm ERR! Invalid: lock file's chokidar@4.0.3 does not satisfy chokidar@3.6.0
    npm ERR! Missing: chokidar@4.0.3 from lock file
    npm ERR! Missing: glob-parent@5.1.2 from lock file
    npm ERR! Invalid: lock file's readdirp@4.1.2 does not satisfy readdirp@3.6.0
    npm ERR! Missing: picomatch@2.3.1 from lock file
    npm ERR! Missing: chokidar@4.0.3 from lock file
    npm ERR! Missing: readdirp@4.1.2 from lock file
    npm ERR! Missing: readdirp@4.1.2 from lock file
    
    $ npm install
    added 7 packages, removed 20 packages, and changed 2 packages in 1s
    
    297 packages are looking for funding
      run `npm fund` for details
    
    $ npm ci
    npm WARN deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
    npm WARN deprecated @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead
    npm WARN deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
    npm WARN deprecated abab@2.0.6: Use your platform's native atob() and btoa() methods instead
    npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
    npm WARN deprecated @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
    npm WARN deprecated domexception@4.0.0: Use your platform's native DOMException instead
    npm WARN deprecated critters@0.0.24: Ownership of Critters has moved to the Nuxt team, who will be maintaining the project going forward. If you'd like to keep using Critters, please switch to the actively-maintained fork at https://github.com/danielroe/beasties
    npm WARN deprecated eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
    
    added 1661 packages in ~2m
    
    297 packages are looking for funding
      run `npm fund` for details
  5. ozz-oss commented on Nov 29, 2025

    @ozz-oss
  6. ozz-oss commented on Nov 29, 2025

    @ozz-oss
  7. michkot commented on Dec 1, 2025

    @michkot

    not sure if it helps, but I have reproduced that on 11.6.2 specifically compared to 11.6.0, a combination of
    npm "ci" "--no-save" "--lockfile-version" "3"

    and a lock-file that is actually version 2 fails with a seemingly weird error complaining about a dependency missing in the lock file (the specific package I found listed as either nested dep or a optional dep).

    With the same version-2 lock file, the following would still pass on 11.6.2
    npm "install" "--no-save" "--lockfile-version" "3"

    ^^ we use these two command lines in build scripts (in CI / local machines), that's why the weird combination of arguments.

    For this specific case, the fix was to actually convert the lockfile to version 3 -- after that the first listed command line works fine.
    I did that with npm "install" "--lockfile-version" "3".

    Maybe it helps shed some light on this issue / on #8669


    edit: However for other projects, it seems the lockfiles are already at version 3, however 11.6.2 expects all optional dependencies to be present in the lockfile

  8. unav4ila8le commented on Dec 5, 2025

    @unav4ila8le

    I am having the same issue. Anyone has any clue for me?
    https://github.com/unav4ila8le/foliofox/actions/runs/19952836659/job/57216226094

  9. carhartl commented on Dec 6, 2025

    @carhartl

    After we‘ve switched to using npm ci, we‘re seeing the same, see here: https://github.com/js-cookie/js-cookie/actions/runs/19975894159/job/57291864207?pr=903

    It is picomatch@4.0.3 in our case.

  10. guilhermejcgois commented on Dec 8, 2025

    @guilhermejcgois

    This comment help me solve the issue with my project: #4934 (comment) ty @lasley

    Now I need to solve a lot of @types/node conflicts, yay 😅

  11. yaronpen commented on Dec 9, 2025

    @yaronpen

    While investigating this issue in our projects, I created a validation tool that checks for inconsistencies between package.json and package-lock.json before running npm ci. It's been helpful for catching these sync problems earlier in our development workflow, especially in CI environments.
    The tool validates:

    Version mismatches between package.json semver ranges and locked versions
    Missing dependencies that should be present in the lockfile
    Structural inconsistencies that cause npm ci to fail

    This is not a fix for the underlying npm bug - it's a diagnostic utility to help identify when lockfiles are out of sync so teams can address issues before CI breaks. I'm sharing it in case it helps others debug their specific cases while waiting for a proper resolution from the npm team.
    Install package: npm install npm-ci-guard
    Github repository:
    https://github.com/yaronpen/npm-ci-guard

  12. 22 remaining items

  13. mwaibel-go commented on Apr 1, 2026

    @mwaibel-go

    For the googlers: Before re-creating the lockfile, you must also remove the node_modules directory. Otherwise the package-lock.json will not be created correctly.

  14. SamStephens commented on Apr 27, 2026

    @SamStephens

    This can also happen if a lockfile is generated with a new version of npm, and then npm ci is run using an older version on npm.

  15. dash61 commented on May 14, 2026

    @dash61

    I had this problem as well when using EAS (an Expo cloud service for React Native) to build my project. It was building in the cloud, using just what I had checked in, and I needed to somehow get the correct npm version and then run npm install twice.

    I managed to fix it after much work by running a pre-install script.
    I was using node.js version 22.22.0, and the expo build was forcing an npm version of 10.9.4. I thought I needed a newer version of npm (I wanted 11.12.1) but it really needed to just run npm install again.

    Add this to package.json in the scripts section: "eas-build-pre-install": "node ./scripts/eas-pre-install.js"
    (make a scripts directory if you don't have one).

    Here is the script eas-pre-install.js (taken from a SO issue here. I changed it a bit.

    #!/usr/bin/env node
    
    const { execSync } = require('child_process');
    
    // Check if the platform is MacOS; if it is darwin, then the build is iOS.
    if (process.platform !== 'darwin') {
        console.log("=====> Not an iOS build, skipping npm installation");
    } else {
        try {
            // Install npm
            execSync('npm install -g npm@11.12.1', { stdio: 'inherit' });
            execSync('npm install', { stdio: 'inherit' });
            console.log("npm installation complete.");
        } catch (error) {
            console.error("Error during npm installation:", error.message);
            process.exit(1); // Exit with a non-zero status if the command fails
        }
    }
    

    After making the script, use the cmd line to set the executable bits: chmod +x eas-pre-install.js

    Hope this helps someone.

  16. added a commit that references this issue on Aug 17, 2026
    d6543e0
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