From bf7db27a3c9631e5c68846bd0682cdc24ecb6a5e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 22:07:00 +0000 Subject: [PATCH 1/5] ci: stop setup-node writing a token that preempts the OIDC exchange `registry-url` makes setup-node write `_authToken=${NODE_AUTH_TOKEN}` to the job's npmrc. Nothing sets that variable now, so npm read a credential, treated auth as configured, skipped the exchange and sent the placeholder: "Unable to authenticate, your authentication token seems to be invalid" (actions/setup-node#1551). registry.npmjs.org is the default, so dropping the input changes nothing else. A token reaching the npmrc again fails the job naming the file, rather than reappearing as a 401 from the registry. The dist-tag failure now says a configuration has to name this workflow file: they are keyed on the filename and grant dist-tag per configuration, so the one covering publish.yaml authorizes nothing here. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Y4EZAEvxLfXuMeNz5ZgCMz --- .github/workflows/promote.yaml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml index 99ffd5e..592ef7d 100644 --- a/.github/workflows/promote.yaml +++ b/.github/workflows/promote.yaml @@ -21,10 +21,13 @@ jobs: steps: - uses: actions/checkout@v4 + # No `registry-url`: it makes setup-node write an `_authToken` line to the + # job's npmrc, and with no NODE_AUTH_TOKEN to expand it npm reads a + # credential, treats auth as configured and never runs the OIDC exchange + # (actions/setup-node#1551). registry.npmjs.org is npm's default anyway. - uses: actions/setup-node@v4 with: node-version: "24.x" - registry-url: "https://registry.npmjs.org" # The runner's bundled npm predates OIDC support for dist-tag, which # npm/cli#10038 added in 11.21.0 and 12.2.0. Pinned rather than `latest`: @@ -34,10 +37,18 @@ jobs: - name: Install npm with OIDC dist-tag support run: npm install -g npm@12.2.0 --ignore-scripts + - name: Check nothing shadows the OIDC exchange + run: | + rc="${NPM_CONFIG_USERCONFIG:-$HOME/.npmrc}" + if [ -f "$rc" ] && grep -q '_authToken' "$rc"; then + echo "::error::$rc configures an auth token, so npm will skip the OIDC exchange and send that token instead. Drop whatever wrote it (usually setup-node's registry-url)." + exit 1 + fi + - name: Promote to latest run: | if ! npm dist-tag add @currents/mcp@${{ github.event.inputs.packageVersion }} latest; then - echo "::error::The registry refused the dist-tag. Trusted publishing grants it separately from publish, and it is off by default: enable 'Allow npm dist-tag' on the trusted publisher configuration for @currents/mcp at https://www.npmjs.com/package/@currents/mcp/access" + echo "::error::The registry refused the dist-tag. Trusted publishing configurations are keyed on the workflow filename and each grants dist-tag separately, so @currents/mcp needs a configuration naming THIS file with 'Allow npm dist-tag' enabled on it — the one for publish.yaml does not carry over. https://www.npmjs.com/package/@currents/mcp/access" exit 1 fi npm dist-tag rm @currents/mcp beta From cf3816ff06d1c5ed4db6d0e1887dfbeec5278418 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 22:11:23 +0000 Subject: [PATCH 2/5] ci: give the announce job a checkout, and let promote re-run linear-release exits before doing anything when there is no `.git` directory, whichever command it is given: `complete` needs a working tree even though it scans no commits. The 2.7.0 promotion moved the tag and then failed here, so the release stayed open in Linear with nothing posted. That failure also had to be recoverable by dispatching again, and was not: `dist-tag rm beta` errors once beta is gone, so a re-run died on the tag it had already removed. Removing it is now conditional. Setting `latest` to the version it already holds already succeeds. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Y4EZAEvxLfXuMeNz5ZgCMz --- .github/workflows/promote.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml index 592ef7d..23d94e0 100644 --- a/.github/workflows/promote.yaml +++ b/.github/workflows/promote.yaml @@ -51,7 +51,9 @@ jobs: echo "::error::The registry refused the dist-tag. Trusted publishing configurations are keyed on the workflow filename and each grants dist-tag separately, so @currents/mcp needs a configuration naming THIS file with 'Allow npm dist-tag' enabled on it — the one for publish.yaml does not carry over. https://www.npmjs.com/package/@currents/mcp/access" exit 1 fi - npm dist-tag rm @currents/mcp beta + if npm dist-tag ls @currents/mcp | grep -q '^beta:'; then + npm dist-tag rm @currents/mcp beta + fi # Separate job so the third-party actions below do not run beside # `id-token: write`: neither needs OIDC, and both resolve a mutable tag. @@ -62,6 +64,12 @@ jobs: contents: read steps: + # linear-release refuses to start outside a git repository, whichever + # command it is given. fetch-depth as its quick start documents. + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Mark Linear release as complete id: release uses: linear/linear-release-action@v0 From 4128eb6f23a67383cda40f3c1fbf19cf347e2088 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 22:12:03 +0000 Subject: [PATCH 3/5] ci: restore registry-url, which does not block the OIDC exchange The 2.7.0 promotion ran with `registry-url` set and exchanged an OIDC credential anyway, so the npmrc token setup-node writes does not preempt it on npm 12.2.0. publish.yaml had been publishing that way all along, which said the same thing before any of this. Dropping it was a guess at an E401 that a missing trusted publisher configuration explains: those are keyed on the workflow filename, and nothing named promote.yaml existed yet. The guard went with it. It only ever asserted the mechanism above, and with registry-url restored it would fail every run on the token setup-node writes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Y4EZAEvxLfXuMeNz5ZgCMz --- .github/workflows/promote.yaml | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml index 23d94e0..74a967a 100644 --- a/.github/workflows/promote.yaml +++ b/.github/workflows/promote.yaml @@ -21,13 +21,10 @@ jobs: steps: - uses: actions/checkout@v4 - # No `registry-url`: it makes setup-node write an `_authToken` line to the - # job's npmrc, and with no NODE_AUTH_TOKEN to expand it npm reads a - # credential, treats auth as configured and never runs the OIDC exchange - # (actions/setup-node#1551). registry.npmjs.org is npm's default anyway. - uses: actions/setup-node@v4 with: node-version: "24.x" + registry-url: "https://registry.npmjs.org" # The runner's bundled npm predates OIDC support for dist-tag, which # npm/cli#10038 added in 11.21.0 and 12.2.0. Pinned rather than `latest`: @@ -37,14 +34,6 @@ jobs: - name: Install npm with OIDC dist-tag support run: npm install -g npm@12.2.0 --ignore-scripts - - name: Check nothing shadows the OIDC exchange - run: | - rc="${NPM_CONFIG_USERCONFIG:-$HOME/.npmrc}" - if [ -f "$rc" ] && grep -q '_authToken' "$rc"; then - echo "::error::$rc configures an auth token, so npm will skip the OIDC exchange and send that token instead. Drop whatever wrote it (usually setup-node's registry-url)." - exit 1 - fi - - name: Promote to latest run: | if ! npm dist-tag add @currents/mcp@${{ github.event.inputs.packageVersion }} latest; then From d42db1085dde82f069889579f5e578583ea1f2ec Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 22:16:30 +0000 Subject: [PATCH 4/5] ci: leave beta on the promoted version Every publish to that channel sets the tag, so it cannot go stale: between releases beta and latest name the same version. Not removing it also leaves the step a single `dist-tag add`, which sets a tag to the version it already holds without complaint, so a promotion that failed after the tag moved can be dispatched again. The conditional that existed to make the removal re-runnable goes with it. The guide said promotion removes the tag, and still described one trusted publisher configuration per package when there is one per workflow file. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Y4EZAEvxLfXuMeNz5ZgCMz --- .github/workflows/promote.yaml | 3 --- RELEASE.md | 18 +++++++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml index 74a967a..33250c9 100644 --- a/.github/workflows/promote.yaml +++ b/.github/workflows/promote.yaml @@ -40,9 +40,6 @@ jobs: echo "::error::The registry refused the dist-tag. Trusted publishing configurations are keyed on the workflow filename and each grants dist-tag separately, so @currents/mcp needs a configuration naming THIS file with 'Allow npm dist-tag' enabled on it — the one for publish.yaml does not carry over. https://www.npmjs.com/package/@currents/mcp/access" exit 1 fi - if npm dist-tag ls @currents/mcp | grep -q '^beta:'; then - npm dist-tag rm @currents/mcp beta - fi # Separate job so the third-party actions below do not run beside # `id-token: write`: neither needs OIDC, and both resolve a mutable tag. diff --git a/RELEASE.md b/RELEASE.md index 91ee3a1..634bcc9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -104,15 +104,23 @@ To promote a beta release to the `latest` tag without re-publishing: The workflow will: 1. Add the `latest` dist-tag to the specified version -2. Remove the `beta` dist-tag from that version -3. Mark the release complete in Linear and post it to Slack +2. Mark the release complete in Linear and post it to Slack + +`beta` is left pointing at the promoted version. The next publish to that +channel moves it, so between releases `beta` and `latest` name the same +version. Re-running the workflow on a version already promoted is therefore +harmless. This is useful when you've tested a beta release and want to make it the default install without rebuilding. The workflow authenticates with npm through Trusted Publishing, so it needs no -`NPM_TOKEN`. Trusted publishing grants dist-tag separately from publish, and the -grant is off by default: **Allow npm dist-tag** has to be enabled on the trusted -publisher configuration for `@currents/mcp`, or the registry refuses the tag. +`NPM_TOKEN`. A trusted publisher configuration names one workflow file, so +`@currents/mcp` carries one per workflow that reaches npm — `publish.yaml` and +`promote.yaml` each have their own, and neither authorizes the other. Each +grants dist-tag separately from publishing, off by default, so a configuration +that promotes needs **Allow npm dist-tag** enabled on it or the registry +refuses the tag. Both are managed per package at +https://www.npmjs.com/package/@currents/mcp/access. ## Linear Release From 7b40346df0c3fcf5327b6876ca451845a62c1797 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 22:27:16 +0000 Subject: [PATCH 5/5] ci: keep the checkout token out of .git/config checkout writes GITHUB_TOKEN into the clone's config by default, where any later step in the job can read it. In announce those steps are linear-release and slack-github-action, both on mutable major tags, which is what putting them in their own job was meant to avoid. Neither job runs git after checking out, so nothing needs the credential. Set on both checkouts rather than the flagged one: the same default applies to the clone the promotion runs in. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Y4EZAEvxLfXuMeNz5ZgCMz --- .github/workflows/promote.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml index 33250c9..76a4352 100644 --- a/.github/workflows/promote.yaml +++ b/.github/workflows/promote.yaml @@ -20,6 +20,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-node@v4 with: @@ -55,6 +57,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + persist-credentials: false - name: Mark Linear release as complete id: release