Skip to content

npm-shrinkwrap.json (5.1.18+) breaks consumers' npm ci on npm 11 — EBADPLATFORM @esbuild/aix-ppc64 #1780

Description

@kylebernhardy

Summary

harper ≥ 5.1.18 publishes an npm-shrinkwrap.json (5.1.9 did not). When a project depends on harper and its lockfile is generated with npm 11 (bundled with current Node 24 releases), npm folds harper's ~1,210-package shrinkwrap subtree into the consumer's package-lock.json nested under node_modules/harper/ — and drops the "optional": true flag from platform-specific packages (@esbuild/*, pulled in via tsx → esbuild).

npm ci then treats @esbuild/aix-ppc64 as a required package on every platform and fails:

npm error code EBADPLATFORM
npm error notsup Unsupported platform for @esbuild/aix-ppc64@0.28.1:
  wanted {"os":"aix","cpu":"ppc64"} (current: {"os":"linux","cpu":"x64"})

To be clear about fault: harper's shrinkwrap itself is correct (it marks these packages "optional": true); the flag is lost in npm 11's reify. npm 12.0.1 handles it properly (the nested subtree collapses to a single entry). But since npm 11 is what Node 24 ships, every consumer with harper as an npm dependency and a stock CI image hits this.

Independent repro

On any macOS/Linux machine with Node 24 + npm 11 (verified with npm 11.8.0 and 11.13.0):

mkdir repro && cd repro
npm init -y
npm install harper@5.1.19        # or 5.1.18 — both ship the shrinkwrap
# lockfile is now poisoned:
node -e "
const l = require('./package-lock.json');
const e = l.packages['node_modules/harper/node_modules/@esbuild/aix-ppc64'];
console.log('entry exists:', !!e, '| optional flag:', e && e.optional);"
# → entry exists: true | optional flag: undefined

rm -rf node_modules
npm ci                            # → EBADPLATFORM (unless you happen to be on AIX/ppc64)

Control: repeat with npx npm@12 install — the lockfile keeps only one harmless nested entry and npx npm@12 ci succeeds. Repeat with harper@5.1.9 (no shrinkwrap) — no nested entries at all, npm 11 works fine.

Impact

Any downstream project that has harper in package.json and runs npm ci in CI with npm 11 (the GitHub Actions default via setup-node + Node 24) gets a hard install failure on every run. Regenerating the lockfile doesn't help — npm 11 reintroduces the bad entries every time.

Options

  1. Stop publishing npm-shrinkwrap.json (return to ≤ 5.1.9 behavior) — consumers get normal resolution; this is what npm docs recommend for libraries (shrinkwrap is designed for CLIs installed standalone, and harper is both, which is the tension here).
  2. Keep it but document that consumers need npm ≥ 12 (engines can't express "the npm that installs your dependents", so this is docs-only).
  3. Upstream: this is arguably an npm/cli reify bug worth filing there too — happy to do that with the repro above if useful.

Workaround we're using in the consumer repo meanwhile: regenerate package-lock with npm 12, install npm 12 in CI before npm ci, and set engines.npm >= 12 + engine-strict=true so an npm 11 developer install can't silently re-poison the lockfile.

Activity

  1. kylebernhardy commented on Jul 14, 2026

    @kylebernhardy
    MemberAuthor

    Investigated this for a fix and hit a design decision that should be yours (@kriszyp) — flagging before anyone implements, since it reverses recent deliberate work.

    #1781 (merged today) only removes the dev-tree symptom. Adding npm prune --omit=dev before npm shrinkwrap drops the tsx → esbuild subtree, so the specific @esbuild/aix-ppc64 entry from this report goes away. Good fix for the reported case.

    But the root cause survives it. harper's runtime dependency tree is full of platform-gated optional native packages, and the shrinkwrap pins all of them. The biggest is @harperfast/rocksdb-js, which declares 8 platform variants as optionalDependencies:

    @harperfast/rocksdb-js-darwin-arm64, -darwin-x64, -linux-arm64-glibc, -linux-arm64-musl,
    -linux-x64-glibc, -linux-x64-musl, -win32-arm64, -win32-x64
    

    plus @lmdb/lmdb-*, @cbor-extract/*, @msgpackr-extract/*, node-unix-socket-*, and fsevents — all os/cpu-restricted runtime deps (verified in the installed tree). When npm 11 folds harper's bundled shrinkwrap into a consumer's package-lock.json and drops "optional": true (the reify bug this issue documents), npm ci will fail EBADPLATFORM on e.g. @harperfast/rocksdb-js-win32-x64 on any non-Windows machine — exactly the esbuild failure mode, just a different package. npm prune --omit=dev can't remove these; harper needs them at runtime.

    So the shrinkwrap and harper's native runtime deps are fundamentally in tension for anyone consuming harper as a dependency (vs. the standalone npm i -g harper CLI install the shrinkwrap was added for in #1622).

    The decision: Option 1 (stop shipping npm-shrinkwrap.json — remove the npm shrinkwrap line from build-tools/build.sh) robustly fixes all consumers on any npm version, and is npm's documented recommendation for a depended-upon package. The cost is that standalone global installs resolve deps via semver rather than a pinned tree (harper's own CI still pins via package-lock.json). Option 2 (keep it, require consumers on npm ≥ 12) leaves the install broken for the npm-11 majority — GitHub Actions' default with Node 24 — so I'd advise against it given the rocksdb-js runtime exposure.

    Since #1622 added the shrinkwrap and #1781 chose keep-and-prune, I don't want to unilaterally rip it out. What's your call — remove, or keep + document npm ≥ 12? Happy to implement + open the PR (patch candidate for 5.1) once you decide.

    Comment generated by kAIle (Claude Opus 4.8)

  2. added a commit that references this issue on Jul 14, 2026
    4a12a68
  3. added a commit that references this issue on Jul 14, 2026
    7f90dee
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

Priority

None yet

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions