Skip to content

ci: prepare release workflow for npm trusted publishing - #136

Merged
jamlen merged 1 commit into
mainfrom
fix/134/trusted-publishing
Aug 12, 2026
Merged

ci: prepare release workflow for npm trusted publishing#136
jamlen merged 1 commit into
mainfrom
fix/134/trusted-publishing

Conversation

@jamlen

@jamlen jamlen commented Aug 12, 2026

Copy link
Copy Markdown
Member

Part of #134. This is the repo-side half only — the trusted publisher must be configured at npmjs.com before it takes effect, which needs a login + 2FA and so can't be done from here. Steps below.

Staged publishing vs trusted publishing

Worth naming the distinction, since the two get conflated:

  • Trusted publishing (OIDC) — replaces the long-lived token with a short-lived, workflow-scoped credential. This is what fixes NPM_TOKEN is invalid — the next real release will fail at verifyConditions #134.
  • Staged publishing — a review gate: npm stage publish, then a maintainer approves with 2FA before it goes live. It complements trusted publishing and explicitly does not remove the need for a token. It also converts semantic-release from fully automated to human-gated on every release.

So staged publishing is a reasonable thing to want, but it's a separate decision and it wouldn't fix the failing release.

What this PR changes

One step, plus a comment explaining why it can't be removed:

- name: Ensure npm supports trusted publishing
  run: |
    npm install -g npm@^11.5.1
    npm --version

Trusted publishing requires npm CLI >= 11.5.1. The release job runs Node 22, which bundles npm 10.9.8 (confirmed against nodejs.org/dist/index.json). Without this step the publish fails after OIDC is configured, in a way that's hard to diagnose.

The reason is specific to how @semantic-release/npm@13.1.5 works. lib/verify-auth.js:

if (await oidcContextEstablished(registry, pkg, context)) {
  return;                                   // ← no token written to .npmrc
}
await setNpmrcAuth(npmrc, registry, context);
await verifyTokenAuth(registry, npmrc, context, pkgRoot);

The exchanged token is used only as a boolean probe — oidcContextEstablished returns !!token and discards it. lib/publish.js then runs npm publish --userconfig <npmrc> against an npmrc containing no credentials, so the npm CLI must perform its own OIDC exchange. An npm that can't do that arrives with nothing at all.

id-token: write was already granted, so no permissions change is needed.

What you need to do at npmjs.com

npmjs.com → deride → Settings → Trusted Publisher → GitHub Actions:

Field Value
Organization or user guzzlerio
Repository deride
Workflow filename release.yml
Environment (leave empty)
Allowed actions npm publish

deride already exists on the registry (2.2.0 is latest), so the "package must already exist" precondition is met.

⚠️ The config pins the workflow filename. Renaming release.yml later breaks publishing with a 404 on the exchange. Noted in CLAUDE.md.

Suggested rollout

Merging this PR is itself a safe live test. semantic-release runs verifyConditions before analysing commits, so the Release workflow exercises the OIDC exchange on every push to main — but this is a ci: commit, so nothing is releasable and nothing publishes.

  1. Configure the trusted publisher (above).
  2. Merge this PR. Watch the Release run for OIDC token exchange with the npm registry succeeded. No publish happens.
  3. Only then merge feat!: constrain stub<T> method names to keyof T #135, which publishes v3.0.0 over the now-proven path.
  4. Once that succeeds, delete the NPM_TOKEN secret.

NPM_TOKEN is deliberately left in place in this PR — the plugin falls back to it when the exchange fails, so it's the rollback until a real publish has gone through. It's currently invalid, so the fallback is a dead end, but removing it now would only change one failure mode for another.

Note that until step 1 is done, every push to main produces a red Release run. That's pre-existing (#134), not introduced here.

Bonus: provenance

CLAUDE.md described NPM_TOKEN as needing "publish + provenance rights", and the workflow commented id-token: write # npm provenance. Provenance was never actually enabled — nothing passes --provenance, there's no publishConfig, and deride@2.2.0 has no attestations. Trusted publishing generates provenance automatically for GitHub Actions, so this finally delivers what that line claimed. CLAUDE.md corrected.

🤖 Generated with Claude Code

Part of #134. This is the repo-side half; the trusted publisher still
has to be configured at npmjs.com before it takes effect.

Adds an explicit npm CLI upgrade to the release job. Trusted publishing
needs npm >= 11.5.1 and Node 22 bundles npm 10.9.8, so without this the
publish fails even once OIDC is configured — and it fails in a
non-obvious way.

@semantic-release/npm 13.1.5 treats a successful OIDC exchange as proof
that authentication is handled and returns early from verifyAuth:

    if (await oidcContextEstablished(registry, pkg, context)) {
      return;                                   // no token written
    }
    await setNpmrcAuth(npmrc, registry, context);
    await verifyTokenAuth(registry, npmrc, context, pkgRoot);

The exchanged token is only used as a boolean probe — it is never
forwarded. publish.js then runs `npm publish --userconfig <npmrc>`
against an npmrc with no credentials in it, so the npm CLI has to
perform its own OIDC exchange. An npm that cannot do that arrives with
nothing.

NPM_TOKEN is deliberately left in place. The plugin falls back to it
when the exchange fails, so it stays as the rollback until a real
publish has succeeded over OIDC.

Also corrects CLAUDE.md, which described NPM_TOKEN as the required
secret "with publish + provenance rights". Provenance was never actually
enabled — nothing passes --provenance, there is no publishConfig, and
deride@2.2.0 has no attestations. Trusted publishing generates it
automatically, so this delivers what that line claimed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jamlen
jamlen merged commit 223a266 into main Aug 12, 2026
13 checks passed
@jamlen
jamlen deleted the fix/134/trusted-publishing branch August 12, 2026 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NPM_TOKEN is invalid — the next real release will fail at verifyConditions

1 participant