Skip to content

[BUG] npm install drops nested lockfile entries inside a bundled dependency that npm ci then requires (npm 10 and 11 alike) #9821

Description

@edreanernst

Is there an existing issue for this?

I searched open and closed issues. #9289 is the closest (same package family, bundled deps at multiple levels) but it reports an ENOENT during extraction, not a lockfile round-trip failure. #8726 / #6787 describe out-of-sync lockfiles generally, without the bundled-dependency cause.

Current Behavior

npm install writes a package-lock.json that npm ci then refuses, on the same npm version, with no changes in between:

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: @opentelemetry/core@2.0.0 from lock file
npm error Missing: @opentelemetry/core@2.0.0 from lock file
npm error Missing: @opentelemetry/core@2.0.0 from lock file
npm error Missing: @opentelemetry/core@2.0.0 from lock file

The four entries npm ci wants are nested inside a bundled dependency's own sub-tree:

node_modules/@aws-amplify/data-construct/node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core
node_modules/@aws-amplify/data-construct/node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/core

(and the same two under @aws-amplify/graphql-api-construct).

@aws-amplify/data-construct declares bundleDependencies: true. Its bundle contains @opentelemetry/core@2.8.0 at the bundle root, but the bundled @opentelemetry/resources@2.0.0 and @opentelemetry/sdk-trace-base@2.0.0 both pin "@opentelemetry/core": "2.0.0" exactly, which 2.8.0 does not satisfy. So the bundle is internally inconsistent.

The two halves of npm disagree about what follows:

  • the install/lockfile writer treats the bundle as opaque and writes no entry for the nested @opentelemetry/core@2.0.0
  • _validateLockfile walks the bundled packages' dependencies specs, finds 2.0.0 unsatisfied, and reports it missing

If the entries are added to the lockfile by hand, npm ci passes and installs cleanly — so the validator's view is the reachable one, and the writer is dropping entries it should keep. A subsequent npm install deletes them again.

Two things make this bite harder than a normal out-of-sync lockfile:

  1. It is not a regression and there is no npm version to pin to. npm 10.8.2 and npm 11.6.2 produce byte-identical pruned lockfiles and both reject them.
  2. It is silent locally. npm install exits 0, and the corrupted lockfile lands in the next commit.

Expected Behavior

npm install followed by npm ci should succeed on the same npm version. Either the lockfile writer keeps the nested entries the validator requires, or the validator does not require entries for packages inside a bundle. Right now neither side gives.

Steps To Reproduce

Six-line package.json, no overrides, no .npmrc:

{
  "name": "repro",
  "version": "1.0.0",
  "dependencies": {
    "@aws-amplify/data-construct": "1.17.7"
  }
}
npm install     # exits 0
npm install     # exits 0 (lockfile has converged)
npm ci          # EUSAGE: Missing: @opentelemetry/core@2.0.0 from lock file (x4)

Reproduces identically with npx -y npm@10.8.2 in place of npm.

Note that a first npm install on a clean directory produces a lockfile missing even more entries (@aws-cdk/toolkit-lib@1.19.0, @aws-cdk/cli-plugin-contract@2.182.2, from deeper bundle levels); a second npm install converges to the four @opentelemetry/core entries and stays there. That is the "run install twice" workaround from #8726 partially working, then stalling.

Environment

npm: 11.6.2 and 10.8.2 (identical behaviour)
Node.js: v22.21.1
OS: Windows 11 Pro 10.0.22631

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