Every job installs through this composite — 36 times per ci.yml run, 8 per
quality.yml run — and each call paid ~80-90 s of npm ci even with setup-node's
npm tarball cache warm (measured 2026-09-01: 3,327 runner-seconds per ci.yml run
just installing). A node_modules cache keyed on runner.os + runner.arch + the
resolved Node version + hashFiles(package-lock.json, .npmrc, postinstall.mjs and
its five helpers) lets an exact hit skip the install entirely.
- No restore-keys, same rule as the ESLint cache (#11600): exact key or a full
npm ci, never a partial tree from another lockfile / Node / postinstall.
- The retry loop is unchanged and remains the miss path; --no-audit --no-fund
because audit:deps is its own gate.
- cache input (default true) lets a caller opt out.
- actions/cache pinned to the v6.1.0 hash already used in nightly-mutation.yml
(zizmor unpinned-uses blanket policy).
- tests/unit/build/npm-ci-retry-composite.test.ts pins the key contents, the
no-restore-keys rule and the miss path.
Refs #8084