ci: standardize on npm 11 with min-release-age gate - #2351
Conversation
Fixes a pre-commit CI failure on PR #2350 where npm ci errored with "Missing: unist-util-visit@4.1.2 from lock file" — Node's bundled npm 10.9.4 resolves an optional nested dependency subtree differently than npm 11 (hast-util-to-estree's optional dependency on unist-util-visit/ unist-util-visit-parents), and hard-fails on the gap where npm 11 tolerates it. Also brings this repo in line with the npm 11 + min-release-age supply-chain hardening rolled out to other Pomerium repos (npm 10 silently ignores min-release-age, so npm 11 is required for it to take effect at all): - .npmrc: min-release-age=3 - package.json: engines.npm and packageManager pinned to npm 11.12.1 - pre-commit.yml: installs npm 11.12.1 before npm ci - npm-audit-signatures.yml: new workflow, verifies registry signatures and provenance attestations on package.json/package-lock.json changes Regenerated package-lock.json with npm 11.12.1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
✅ Deploy Preview for pomerium-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Greptile SummaryThe PR standardizes development and CI on Node 24 with npm 11 and adds a three-day package-release quarantine.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| .github/workflows/npm-audit-signatures.yml | Adds a least-privilege workflow that installs dependencies without lifecycle scripts and audits registry signatures and provenance. |
| .npmrc | Configures npm 11 to exclude packages published within the preceding three days. |
| .tool-versions | Pins Node 24.19.0 so local and CI environments receive the intended npm 11 runtime. |
| package.json | Raises the supported Node floor and constrains npm to major version 11. |
| package-lock.json | Regenerates dependency metadata under npm 11, including removal of the problematic optional nested subtree. |
Reviews (6): Last reviewed commit: "ci: drop packageManager pin and corepack..." | Re-trigger Greptile
npm ci was failing because postman-code-generators (a transitive dep of docusaurus-theme-openapi-docs) shells out to `yarn install` from its postinstall script. Node's bundled corepack shim for yarn refuses to run once package.json declares a "packageManager" field (added in 1c198e6 for npm 11), even though that field only pins npm -- it just needs `corepack enable` to be run first, as its own error message says. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kenjenkins
left a comment
There was a problem hiding this comment.
I thought npm was bundled with nodejs? Do we need to bump the nodejs version in the .tool-versions file?
We can definitely bump to node 24 (Netlify supports it) and get npm 11 with it. I was just going the "standard" way what we did in other repos when we added npm 11. |
Node 24 bundles npm 11 by default (24.19.0 ships npm 11.17.0), matching the convention already used in pomerium/pomerium. This makes the curl-based "install npm 11.12.1" workaround in pre-commit.yml and npm-audit-signatures.yml unnecessary — it was only needed because Node 22 bundles npm 10, and `corepack enable` does not shim npm on its own (only yarn/pnpm). - .tool-versions: nodejs 22.22.0 -> 24.19.0 - pre-commit.yml, npm-audit-signatures.yml: drop the manual npm install/verify steps, rely on Node's bundled npm - package.json: bump engines.node floor to >=24.0.0 (the version where npm 11 becomes bundled), so it reflects what's actually required to avoid the npm 10 lockfile-resolution gap from #2350 Verified locally under node 24.19.0 (bundled npm 11.17.0): npm ci, npm ci --ignore-scripts, and npm audit signatures --min-release-age=0 all pass. Follow-up to #2351, in response to review feedback questioning why Node wasn't just bumped to get npm 11 natively. AI usage: Claude Code (Sonnet 5) investigated the fix, confirmed against pomerium/pomerium's convention and Node's release metadata, and verified the change locally; changes reviewed by Nick Taylor. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@kenjenkins, I just moved to Node.js 24. No need to stay on 22. |
The `packageManager: "npm@11.12.1"` field wasn't doing anything: Corepack only manages npm from it if you run `corepack enable npm` (nothing here does that, only bare `corepack enable`), npm itself doesn't validate its own version against the field, and Netlify's docs confirm it uses this field via Corepack for Yarn/pnpm but not for npm. It was also already stale (11.12.1 vs the 11.17.0 actually bundled with Node 24.19.0) and would keep drifting with every Node point release. Removing it also removes the reason the `corepack enable` step in pre-commit.yml existed: that step was only needed because our own `packageManager` field is what triggered the disabled yarn shim's check when postman-code-generators's postinstall shells out to `yarn install`. With no `packageManager` field declared anywhere in the ancestor chain, the shim doesn't trigger the check. Verified with a clean copy of package.json/package-lock.json under Node 24.19.0, Corepack explicitly disabled: `npm ci` (with scripts) completes successfully, including postman-code-generators's yarn-based postinstall populating its codegens/ folders. Reran against the actual repo tree with the same result. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
9604f87 to
d99e310
Compare
There was a problem hiding this comment.
I don't see a min-release-age in any of our other repos. Should we hold off on adding this for now, for consistency?
There was a problem hiding this comment.
I had it on auto-merge. 😅 I took that from the issue Bobby had created initially I believe, but you're right. It's not in our other repos. I can rollback the .npmrc change.

Summary
The
pre-commitCI failure on #2350 surfaced that this repo had never actually been migrated to npm 11 as part of ENG-3817's package-age-quarantine rollout — it was still on Yarn when that work was scoped, so it got missed.npm cierrored with:Root cause: Node's bundled npm (10.9.4, from
.tool-versions' pinned Node 22.22.0) resolves an optional nested dependency subtree differently than npm 11 (hast-util-to-estree's optional dependency onunist-util-visit/unist-util-visit-parents) — npm 11 tolerates the gap, npm 10.9.4 hard-failsnpm cion it. Confirmed by reproducing the failure locally withnpx npm@10.9.4 ciagainst a freshly-regenerated lockfile, and confirming success with npm 11.This PR brings the repo onto npm 11 by bumping Node itself, rather than pinning npm separately:
.tool-versionsnow pins Node 24.19.0, which bundles npm 11.17.0 by default (matching the convention already used inpomerium/pomerium). That also meansmin-release-age(which npm 10 silently ignores) actually takes effect now, bringing this repo in line with the supply-chain hardening already rolled out to other Pomerium repos.Changes
.tool-versions:nodejs 22.22.0→24.19.0— Node's bundled npm 11 is what actually fixes thenpm cifailure; no separate npm install step needed.npmrc:min-release-age=3package.json:engines.nodebumped to>=24.0.0(the version where npm 11 becomes the bundled default),engines.npmpinned to>=11 <12— the<12ceiling is deliberate: npm 12.0.2 already exists on the registry, but no Node release line bundles it yet, so this avoids picking up an unvetted major version by accidentpackageManagerfield — Corepack only manages npm from this field if you explicitly runcorepack enable npm(this repo doesn't), npm itself doesn't validate its own version against it, and Netlify's docs confirm it's consulted via Corepack for Yarn/pnpm but not for npm, so pinning it here wouldn't have done anything except go stalenpm-audit-signatures.yml(new): verifies registry signatures and provenance attestations onpackage.json/package-lock.jsonchangespackage-lock.jsonregenerated with npm 11Locally verified under Node 24.19.0 (bundled npm 11.17.0), with no manual npm install and no
corepack enable:npm ci,npm ci --ignore-scripts,npm audit signatures --min-release-age=0, andnpm run checkall pass — includingpostman-code-generators's yarn-based postinstall script (a transitive dep ofdocusaurus-theme-openapi-docs), which runs fine since nothing in the ancestorpackage.jsonchain declares apackageManagerfield to trigger Node's disabled yarn shim.Related
AI disclosure
Claude Code (Sonnet 5) was used throughout, iteratively rather than as a single draft-and-review pass: it investigated the original CI failure, and initial attempts at a fix (regenerating the lockfile, then just bumping npm) didn't hold up under scrutiny, which led to jointly digging further and finding the actual npm 10 vs 11 dependency-resolution root cause. That led to cross-checking the convention used in other Pomerium repos (ENG-3817) and, after review feedback questioned why Node wasn't just bumped, confirming against Node's own release metadata and
pomerium/pomerium's existing.tool-versions/CI convention that bumping to Node 24 gets npm 11 natively. Along the way, several intermediate fixes (apackageManagerpin, a curl-installed npm binary, acorepack enablestep) turned out to be unnecessary once Node was bumped and were removed after verifying locally (clean installs, with Corepack explicitly disabled, confirmingnpm ciincluding the yarn-based postinstall still succeeds). Nick Taylor directed the investigation and design decisions at each step and reviewed all resulting changes.Also needed to manually intervene here. We're not using a netlify.toml so had to modify the build command in the Netlify UI. We already moved to npm a while back, but the build command was not updated.
Also set the Node version to 24.x in the Netlify UI
Checklist