Repository navigation
ci(release): publish with Node 24's bundled npm instead of installing one - #5105
Conversation
… one OIDC trusted publishing needs npm >= 11.5.1, and Node 22 bundles npm 10.x, so publish-npm installed npm@11.20.0 next to id-token: write. It now runs an exactly pinned Node 24.21.0, whose bundled npm (11.19.0) meets the floor, and installs nothing; a step fails the job before publishing if the bundled npm is ever below 11.5.1. The build jobs keep Node 22. The release skill's note on pinning moves from the npm CLI to the Node release. Closes #5104 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
|
There was a problem hiding this comment.
🟡 Changes recommended
The new release-critical npm version guard lacks committed regression coverage.
1 open finding
What changed in this PR
Updates npm publishing to use Node 24’s bundled OIDC-capable npm without installing dependencies in the credentialed job.
Changes:
- Pins the publish job to Node 24.21.0 and validates npm’s minimum version.
- Updates release guidance for deliberately bumping the pinned Node release.
| File | Description |
|---|---|
.github/workflows/release.yml |
Removes npm installation and adds the bundled npm version guard. |
.claude/skills/release/SKILL.md |
Documents Node pin maintenance. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| - name: Check the bundled npm supports OIDC trusted publishing | ||
| run: | | ||
| NPM_VERSION="$(npm --version)" | ||
| echo "node $(node --version), npm $NPM_VERSION" | ||
| node -e ' |
There was a problem hiding this comment.
Added scripts/release-npm-floor.test.mjs (4d13264 on #5105, cherry-picked on #5106). Like release-dist-tag.test.mjs, it reads publish-npm out of release.yml and runs the floor step itself, with a stub npm on PATH: 11.5.1, 11.6.0, 11.19.0 and 12.0.0 pass; 11.5.0, 11.4.9, 10.9.9 and 9.0.0 fail with "below 11.5.1". It also asserts the job pins Node exactly to a release whose bundled npm meets the floor, runs the check before the download and the publish, and has no step that installs or runs a fetched package (comment lines ignored). The previous workflow fails all of these; loosening the floor to 11.5.0 fails the refusal test. A follow-up commit adds one more shape check: the publish step hands npm the tarball as ./release-artifact/*.tgz (a bare dir/file is read as a GitHub repo, which is how the MCP Inspector's first split-job release failed, modelcontextprotocol/inspector#2551).
…ll shape scripts/release-npm-floor.test.mjs reads publish-npm out of release.yml, as release-dist-tag.test.mjs does, and runs the npm floor step with a stub npm on PATH: 11.5.1, 11.6.0, 11.19.0 and 12.0.0 pass, 11.5.0, 11.4.9, 10.9.9 and 9.0.0 fail. It also asserts an exact Node pin whose bundled npm meets the floor, the check ahead of the download and the publish, and no step that installs or runs a fetched package (comment lines ignored). The previous workflow fails all five; loosening the floor to 11.5.0 fails the refusal test (Copilot on #5105 and #5106). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
…ll shape scripts/release-npm-floor.test.mjs reads publish-npm out of release.yml, as release-dist-tag.test.mjs does, and runs the npm floor step with a stub npm on PATH: 11.5.1, 11.6.0, 11.19.0 and 12.0.0 pass, 11.5.0, 11.4.9, 10.9.9 and 9.0.0 fail. It also asserts an exact Node pin whose bundled npm meets the floor, the check ahead of the download and the publish, and no step that installs or runs a fetched package (comment lines ignored). The previous workflow fails all five; loosening the floor to 11.5.0 fails the refusal test (Copilot on #5105 and #5106). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com> (cherry picked from commit 4d13264) Signed-off-by: cliffhall <cliff@futurescale.com>
npm publish reads a bare dir/file argument as GitHub owner/repo shorthand and tries to clone it over SSH; the MCP Inspector's first release through the same split publish job failed that way (modelcontextprotocol/inspector#2551). release.yml already passes ./release-artifact/*.tgz; this asserts it, so the ./ cannot be lost in an edit. Reproduced locally with Node 24.21.0's npm: the bare path exits 128 on 'git ls-remote ssh://git@github.com/release-artifact/...', the ./ path dry-run-publishes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
|
Copilot round 1: 1 finding (medium), fixed: |
|
Copilot round 2: approval recommended, no new comments (the only listed finding is round 1's, answered and fixed). Loop ends. Merging. |

Closes #5104
Description
OIDC trusted publishing needs npm >= 11.5.1, and Node 22 bundles npm 10.x, so
release.yml'spublish-npmjob rannpm install -g --ignore-scripts npm@11.20.0in the job that holdsid-token: write. The CLI it fetched was then executed next to the credential, against theAGENTS.mdrule that a job holding a publish credential installs nothing.Now:
publish-npmrunsactions/setup-node(still SHA-pinned) with Node24.21.0, pinned exactly, whose bundled npm is 11.19.0. There is nonpm installstep.node -e) and runsnpm publishon it.Found by Copilot on the v1.0.0 merge PR #5090 (thread r4237133436).
Server Details
.github/workflows/release.yml(publish-npm),.claude/skills/release/SKILL.mdMotivation and Context
#5104: the publish job should run no registry-fetched code next to the OIDC credential.
How Has This Been Tested?
The workflow only runs on a published Release, so the probes reproduce its steps locally:
Breaking Changes
None. The published packages are unchanged; only the runtime of the publish job changes.
Types of changes
Checklist
🤖 Generated with Claude Code