Trusted Publishing Admin Runbook
One-time setup and release operations for npm Trusted Publishing with GitHub Actions, integrated with Changesets.
The decision this runbook implements — a tag is the only thing that publishes, the credential is minted per run, versions are independent — is ADR 0025.
This runbook is for npm/package admins and repository admins who manage release security and publication settings.
Preflight (must match repository state)
Section titled “Preflight (must match repository state)”Verify these files before setting publishers on npmjs.com:
.github/workflows/release.yml- the publish step runs
pnpm run release(notrelease:rehearse, which is a local dry run) - workflow permissions include
id-token: write - no
NODE_AUTH_TOKEN/NPM_TOKENin publish env - npm is upgraded to ≥ 11.5 in the job; the OIDC exchange does not exist in older CLIs
- the publish step runs
package.json- includes
version: changeset version && pnpm install --lockfile-only - includes
releaseandrelease:rehearsescripts
- includes
.changeset/config.jsonbaseBranchismainfixedis empty: each package moves by its own changesets
If any of these checks fail, stop and align the repo first.
Step 1: Configure npm Trusted Publisher
Section titled “Step 1: Configure npm Trusted Publisher”Repeat for each publishable package — the list scripts/publish-workspace.mjs asserts against, plus @modyra/angular:
- Open package on npmjs.com.
- Go to Package Settings, then Trusted Publishing (or Publishing Provider).
- Select provider: GitHub Actions.
- Fill fields exactly:
- Organization/User: your GitHub org or user
- Repository:
modyra - Workflow filename:
release.yml - Environment name: leave empty unless your workflow uses GitHub Environments
- Allowed action:
npm stage publish
- Save.
Notes:
- Field values are case-sensitive.
- Trusted publishing supports GitHub-hosted runners only.
- Each package has one trusted publisher configuration at a time.
A package npm has never seen
Section titled “A package npm has never seen”npm has no settings page for a name that does not exist, so a brand-new package cannot be configured in advance. Publish its first version once as an authenticated maintainer:
npm loginnpm publish --access public # from the package directoryThen configure its trusted publisher as above. Every later version comes from the workflow.
Step 2: Changesets release flow (what to expect)
Section titled “Step 2: Changesets release flow (what to expect)”This repo is Changesets-driven:
- Contributors add a changeset file under
.changeset/for user-facing changes. - A maintainer creates/updates the “Version Packages” PR by running
pnpm changeset versionand committing the result — ornpm run release:integrate, which also commits and tags. - Pushing the
vX.Y.Ztag (npm run release:integrate:push) starts the release; a push tomaindoes not. - The workflow runs the full gate, then
pnpm run release, which executes:node scripts/publish-workspace.mjsnode scripts/publish-angular.mjs
- Each package is staged at the version its own
package.jsondeclares, with--provenance. - A maintainer makes the staged versions public with proof of presence:
npm stage listnpm stage view <stage-id>npm stage approve <stage-id>Until that step the registry still serves the previous version.
Why manual PR creation: organization policy disables GitHub Actions PR creation/approval with GITHUB_TOKEN.
Step 3: Post-setup verification
Section titled “Step 3: Post-setup verification”Run these checks after the first release:
- The release job succeeds without npm token secrets.
npm stage listholds every package at the version itspackage.jsondeclares in the tagged commit; after approval,npm viewagrees.- Package metadata shows a provenance attestation once approved.
Useful checks:
npm view @modyra/core versionnpm view @modyra/angular versionnpm view @modyra/core --json | grep -i attestationA green job is evidence of staging, not of publication: both publishers read the staging area back and fail when a package they staged is not in it. What makes a version public is the approval step.
Step 4: Harden security after first success
Section titled “Step 4: Harden security after first success”After confirming OIDC trusted publishing works:
- Confirm no
NPM_TOKENexists in repository secrets. - In npm package settings, restrict token-based publish access.
- Keep 2FA enforced for maintainers.
Troubleshooting
Section titled “Troubleshooting”If publish fails with ENEEDAUTH or OIDC-related auth errors:
- Re-check trusted publisher fields for exact match (
modyra,release.yml, actionnpm stage publish). A publisher that permits only the staged action answers a directnpm publishwith403 OIDC permission denied. - Confirm release is running in
.github/workflows/release.yml(not via a different caller workflow). - Confirm workflow still has
permissions: id-token: write. - Confirm runner is GitHub-hosted and npm is ≥ 11.5.
If only some packages publish:
- Verify a trusted publisher exists for each one; a missing publisher stops the run partway and leaves the registry holding some new versions and not others.
- Re-run the same release —
workflow_dispatchis enabled for this. The publishers skip what is already published and fail on version mismatches, so repeating is the intended repair.
If the run fails before publishing anything with “no longer matches the workspace”:
scripts/publish-workspace.mjs found a public package it does not list, or lists one that is now private. Fix the list; the release published nothing.