Skip to content

[BUG] npm ci rejects a lockfile just generated by npm install for an unavailable optional dependency #9846

Description

@kotering

Is there an existing issue for this?

  • I have searched the existing issues

Related issues include #8767 and #8726, but both are closed. In #8767, a recurrence after the previous fix was reported and a contributor requested a new issue with a minimal reproduction. This reproduction also appears related to the fix in #9083.

This issue exists in the latest npm version

  • I am using the latest npm

Reproduced with npm 12.0.2.

This is not just a request to bump a dependency for a CVE

  • This is not solely a request to bump a dependency for a CVE

Current Behavior

When a package declares an optional dependency that is unavailable from the registry, npm 12.0.2 completes a normal npm install and generates package-lock.json. An immediate npm ci using the same npm version rejects that unmodified lockfile as out of sync.

The minimal reproduction uses mdream@1.5.12, which declares the unpublished @mdream/rust-wasm32-wasi@1.5.12 package in optionalDependencies.

npm error code EUSAGE
npm error `npm ci` can only install packages when your package.json and package-lock.json are in sync.
npm error Missing: @mdream/rust-wasm32-wasi@ from lock file

The result differs by npm version:

npm version npm install Subsequent npm ci WASI placeholder in lockfile
11.13.0 succeeds succeeds generated
11.16.0 succeeds fails with EUSAGE not generated
12.0.2 succeeds fails with EUSAGE not generated

npm 11.13.0 adds this inert placeholder entry and accepts the lockfile:

"node_modules/mdream/node_modules/@mdream/rust-wasm32-wasi": {
  "optional": true
}

npm 11.16.0 and 12.0.2 do not add the placeholder and then report that dependency as missing during npm ci.

Adding --omit=optional to npm ci does not avoid the error because lockfile validation happens first.

Expected Behavior

npm ci should accept an unmodified package-lock.json generated moments earlier by npm install with the same npm version and configuration.

An unavailable optional dependency should either be represented consistently in the generated lockfile or ignored consistently during npm ci validation.

This appears to be a regression of the optional-dependency handling addressed by #9083 (fix(ci): don't error on optional deps in the lockfile).

Steps To Reproduce

Minimal reproduction repository:

https://github.com/kotering/mdream-npm-ci-reproduction

  1. Clone the repository into a fresh directory.

  2. Run a normal install using the latest npm:

    npx --yes npm@12.0.2 install --ignore-scripts --no-audit --no-fund
  3. Without modifying package.json or the generated package-lock.json, run:

    npx --yes npm@12.0.2 ci --dry-run --ignore-scripts --no-audit --no-fund
  4. Observe EUSAGE and Missing: @mdream/rust-wasm32-wasi@ from lock file.

  5. The same sequence fails with npm 11.16.0. In a separate fresh checkout, the same sequence succeeds with npm 11.13.0.

Environment

  • npm: 12.0.2; also reproduced with 11.16.0
  • Node.js: 24.18.0
  • OS Name: macOS 26.5.2
  • System Model Name: Apple Silicon (arm64)
  • npm registry: https://registry.npmjs.org/

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions