Skip to content

ci(release): publish with Node 24's bundled npm instead of installing one - #5105

Merged
cliffhall merged 3 commits into
v2/mainfrom
v2/chore/5104-release-publish-bundled-npm
Oct 10, 2026
Merged

cliffhall merged 3 commits into
v2/mainfrom
v2/chore/5104-release-publish-bundled-npm

Conversation

@cliffhall

Copy link
Copy Markdown
Member

Closes #5104

Description

OIDC trusted publishing needs npm >= 11.5.1, and Node 22 bundles npm 10.x, so release.yml's publish-npm job ran npm install -g --ignore-scripts npm@11.20.0 in the job that holds id-token: write. The CLI it fetched was then executed next to the credential, against the AGENTS.md rule that a job holding a publish credential installs nothing.

Now:

  • publish-npm runs actions/setup-node (still SHA-pinned) with Node 24.21.0, pinned exactly, whose bundled npm is 11.19.0. There is no npm install step.
  • A new step, before the artifact is downloaded or anything is published, fails the job if the bundled npm is below 11.5.1 (a different Node pin, a runner override).
  • The build jobs keep Node 22; the publish job only reads the tarball's manifest (node -e) and runs npm publish on it.
  • The release skill's note on pinning moves from the npm CLI to the Node release.

Found by Copilot on the v1.0.0 merge PR #5090 (thread r4237133436).

Server Details

  • Server: none (repository-wide)
  • Changes to: .github/workflows/release.yml (publish-npm), .claude/skills/release/SKILL.md

Motivation 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:

# Node 24.21.0 (installed with `n` in a temp prefix)
$ node --version; npm --version
v24.21.0
11.19.0
# the new floor check, as in the workflow step
11.19.0 -> exit 0   11.5.1 -> exit 0   12.0.0 -> exit 0   11.5.0 -> exit 1   10.9.9 -> exit 1
# Node 24.21.0's bundled npm publishing a real tarball, as the job does (dry run)
$ npm publish ./modelcontextprotocol-server-memory-1.0.0.tgz --dry-run --access public --tag latest --registry https://registry.npmjs.org/
npm notice name: @modelcontextprotocol/server-memory
npm notice version: 1.0.0
npm notice Publishing to https://registry.npmjs.org/ with tag latest and public access (dry-run)
$ node scripts/verify-action-pins.mjs
verify:action-pins — OK (9 credentialed job(s) across 11 workflows, every action SHA-pinned)
$ node --test scripts/release-dist-tag.test.mjs        # pass 12, fail 0
$ node --test scripts/lib/workflow-gate.test.mjs       # pass 51, fail 0
$ npm run local:gate; echo EXIT=$?
EXIT=0

Breaking Changes

None. The published packages are unchanged; only the runtime of the publish job changes.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have read the MCP Protocol Documentation (not applicable: CI)
  • My changes follow MCP security best practices (fewer moving parts next to the publish credential)
  • I have updated the server's README accordingly (not applicable)
  • I have added a changeset (not applicable: nothing published changes)
  • I have tested this with an LLM client (not applicable: release workflow; probes above)
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling (the npm floor check fails closed)
  • I have documented all environment variables and configuration options (not applicable)

🤖 Generated with Claude Code

… 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>
@cliffhall cliffhall added the v2 label Oct 10, 2026
@changeset-bot

changeset-bot Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 28b468e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Comment on lines +231 to +235
- name: Check the bundled npm supports OIDC trusted publishing
run: |
NPM_VERSION="$(npm --version)"
echo "node $(node --version), npm $NPM_VERSION"
node -e '

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
cliffhall added a commit that referenced this pull request Oct 10, 2026
…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>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 1: 1 finding (medium), fixed: scripts/release-npm-floor.test.mjs runs the workflow's npm floor step against boundary versions and pins the publish job's shape (exact Node pin, check first, no install, ./ tarball path). Gate EXIT=0. Requesting round 2.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The credentialed job no longer installs packages, and committed tests cover the npm floor and publishing safeguards.

1 open finding

🧠 Review effort: Balanced

@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 2: approval recommended, no new comments (the only listed finding is round 1's, answered and fixed). Loop ends. Merging.

@cliffhall
cliffhall merged commit e718eca into v2/main Oct 10, 2026
33 checks passed
@cliffhall
cliffhall deleted the v2/chore/5104-release-publish-bundled-npm branch October 10, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

release.yml: the npm publish job installs the npm CLI next to the OIDC credential

2 participants