From 10ec5bdc3d82525ee30d0e71c34d01c8723dd702 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 1 Aug 2026 23:22:52 -0400 Subject: [PATCH] ci: sync the release workflow on v2/main with main's verbatim (#1902) main's copy of .github/workflows/main.yml carried three release fixes v2/main never took -- #1831 (least-privilege default GITHUB_TOKEN scope), #1834 (derive the npm dist-tag from the version) and #1836 (publish via npm OIDC trusted publishing). Diffing the file both directions shows v2/main has NOTHING main lacks: its only unique content is the superseded publish step, run: npm publish --access public --provenance env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} which is exactly what #1836 replaced. Every other line already matched. So this is a verbatim copy of main's file rather than a hunk-by-hunk forward-port -- fewer ways to get it wrong, and it leaves the two branches byte-identical (`git diff origin/main -- .github/workflows` is now empty). Not a back-merge: only this one file is taken, so none of main's pre-swap v1 lineage enters v2/main's ancestry (see #1868). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU --- .github/workflows/main.yml | 41 +++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5fdc748db..71e9ec4c9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,6 +8,15 @@ on: release: types: [published] +# Default least-privilege scope for GITHUB_TOKEN. Without this, jobs inherit the +# repository's default token permissions, which are broader than any job here +# needs (CodeQL `actions/missing-workflow-permissions`). The `publish` and +# `publish-github-container-registry` jobs declare their own blocks below, which +# override this one entirely rather than adding to it — so each publish job must +# continue to list every scope it needs, including `contents: read`. +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest @@ -145,6 +154,11 @@ jobs: exit 1 fi + # OIDC trusted publishing requires npm >= 11.5.1; Node 22's bundled npm is + # 10.x, which fails with ENEEDAUTH before OIDC is ever attempted. + - name: Ensure npm CLI supports OIDC trusted publishing + run: npm install -g npm@^11.5.1 + - name: Install dependencies (root + all clients) run: npm install @@ -160,9 +174,30 @@ jobs: # prepack); the redundancy is intentional — each is a clean-tree rebuild # and the `prepack` one is what actually populates the published tarball, # so don't "optimize" it away. - run: npm publish --access public --provenance - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + # + # The dist-tag is derived from the version, and passing it explicitly is + # NOT optional: `npm publish` defaults to `--tag latest` regardless of + # semver prerelease status, so publishing `2.0.0-rc.1` without this would + # point every `npx @modelcontextprotocol/inspector` at a release + # candidate. A prerelease is a hyphen after the patch component + # (`2.0.0-rc.1`); build metadata uses `+` and is not a prerelease. Done + # in shell rather than with `semver` because that package is only a + # transitive dependency here and must not be relied on in CI. + # + # There is deliberately NO `NODE_AUTH_TOKEN` here. Publishing uses npm + # OIDC trusted publishing (`id-token: write` + `environment: release`), + # which needs no token — and the repo has no `NPM_TOKEN` secret. Setting + # it from a non-existent secret writes an EMPTY `_authToken` into the + # `.npmrc` that `setup-node` generates, and npm then fails `ENEEDAUTH` + # before OIDC is ever attempted. Do not "restore" it. + run: | + VERSION="$(node -p "require('./package.json').version")" + case "$VERSION" in + *-*) NPM_TAG=next ;; + *) NPM_TAG=latest ;; + esac + echo "Publishing $VERSION under dist-tag '$NPM_TAG'" + npm publish --access public --provenance --tag "$NPM_TAG" # Build and push the multi-arch container image to GHCR on a published # release. The image installs the packed tarball (`Dockerfile`) so it ships