npm publish --provenance Failed: the 8 Most Common Reasons and How to Fix Each
The eight failure shapes we see across npm/cli and actions/runner issues when --provenance breaks: OIDC audience drift, Sigstore Fulcio errors, self-hosted runner rejections, and the package.json repository.url mismatch that takes the longest to find.
Sources: S-001 S-002 S-003 S-004 S-005
npm publish --provenance succeeded all summer, then failed at 2 AM with E_NOATTEST or unable to mint provenance. The eight failure shapes below cover what we have seen in npm/cli issues (S-005) and the GitHub Actions OIDC docs (S-002), with the exact fix for each.
TLDR
The eight most common reasons:
id-token: writeis missing or scoped wrong.registry-urlis unset or wrong.actions/setup-nodeis older than v4.NPM_TOKENis still in the env.package.json#repository.urldoes not match the GitHub repo.- Self-hosted runner; npm rejects the OIDC issuer.
- Workflow is in a fork, not the upstream repo configured as trusted publisher.
- Sigstore Fulcio outage or cert rotation.
1. id-token: write missing or scoped wrong
Symptom: workflow run logs say unable to mint provenance or fall back to legacy publish silently.
Cause: without permissions.id-token: write, GitHub does not issue an OIDC token, so npm’s CLI cannot include provenance.
Fix:
permissions:
id-token: write
contents: read
at the workflow or job level. If you use reusable workflows, the inner workflow needs it too — permissions does not auto-propagate.
Source: GitHub OIDC for npm (S-002).
2. registry-url unset or wrong
Symptom: publish succeeds but no provenance badge; the npm CLI says provenance not attested.
Cause: without registry-url, the OIDC audience claim does not point at npm, so the token is technically valid but useless to the registry.
Fix:
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: "https://registry.npmjs.org"
The URL is literal — https://registry.npmjs.org (no trailing slash, no path). For private registries with their own OIDC support, use their documented URL.
Source: npm trusted-publisher docs (S-001).
3. actions/setup-node older than v4
Symptom: same as #2 — OIDC silently does not engage.
Cause: setup-node v3 wires registry-url for legacy auth but not the OIDC audience flow.
Fix: bump to actions/setup-node@v4 and rerun. Also pin a Node version that ships with npm@9.5 or newer — provenance is not available before that (npm CLI releases — S-003).
4. NPM_TOKEN still in the env
Symptom: publish succeeds, no provenance, no trusted-publisher use, no obvious error in the log.
Cause: npm CLI prefers an explicit _authToken or NODE_AUTH_TOKEN over the OIDC flow. Trusted publishing is silently bypassed.
Fix: delete every reference to NPM_TOKEN, NODE_AUTH_TOKEN, and _authToken from .github/workflows/*, .npmrc, and .npmrc.template. After deleting, rerun a pre-release. The provenance badge must appear.
This is the single most common failure shape in the threads we read on npm/cli (S-005).
5. package.json#repository.url does not match the GitHub repo
Symptom: npm rejects the publish with a trusted-publisher mismatch error, or accepts the publish but emits a warning that the repository URL in the manifest does not match the workflow source.
Cause: npm matches the trusted publisher against the GitHub run identity and the repository.url in the manifest. If repository.url points at a fork or an old org, the check fails.
Fix:
"repository": {
"type": "git",
"url": "git+https://github.com/<owner>/<repo>.git"
}
Owner and repo must match exactly what is registered as the trusted publisher on npm.
6. Self-hosted runner; npm rejects the OIDC issuer
Symptom: provenance step fails with unsupported OIDC issuer or audience mismatch.
Cause: GitHub-hosted runners (runs-on: ubuntu-latest) issue OIDC tokens with iss=https://token.actions.githubusercontent.com, which npm trusts. Self-hosted runners may issue tokens that npm currently rejects unless GitHub has wired the same issuer.
Fix: for now, use GitHub-hosted runners for the publish job. You can keep CI on self-hosted and split publishing into a separate job pinned to ubuntu-latest. (GitHub OIDC docs — S-002)
7. Workflow is in a fork, not the upstream repo
Symptom: contributors open PRs that try to publish; the workflow runs on the fork, npm rejects the trusted-publisher claim.
Cause: trusted publisher is configured for upstream/repo. PR runs come from fork/repo and the OIDC repository_id claim does not match.
Fix: gate publish on the release or push.tags event from the upstream branch, not on pull_request. PR builds should not attempt to publish in the first place.
on:
release:
types: [published]
8. Sigstore Fulcio outage or cert rotation
Symptom: provenance step fails with unable to obtain Fulcio cert or a network error against fulcio.sigstore.dev.
Cause: Sigstore’s Fulcio CA had a transient outage or a cert rotation that broke a pinned chain (Sigstore docs — S-004).
Fix: retry. Check the Sigstore status page before assuming your config broke. If retries fail for an hour, fall back to npm publish without --provenance for the urgent release and republish provenance separately when the service is back — but only after confirming the rest of the trusted-publisher flow worked.
Decision tree
| Symptom | Likely cause | First check |
|---|---|---|
| ”unable to mint provenance” | id-token permission | Workflow permissions: block |
| Publish OK, no badge | NPM_TOKEN present OR registry-url missing | .npmrc, workflow env |
| ”audience mismatch” | self-hosted runner OR wrong registry-url | runs-on: and registry-url: |
| ”trusted publisher mismatch” | repository.url stale OR fork PR | package.json and trigger event |
| ”Fulcio cert error” | Sigstore outage | status.sigstore.dev |
Common mistakes
- Pinning
actions/setup-node@v4.0.0and forgetting it has known OIDC issues fixed in later v4 patches. Pin tov4(the major), or pin to a specific SHA after testing. - Trying to debug by adding
echo $NPM_TOKENto the workflow. Tokens are masked, but the surrounding env is not always — leak risk. - Running the publish locally to “verify the package works” with a personal
NPM_TOKENand then accidentally publishing. Usenpm publish --dry-runfor local checks.
FAQ
Can I see whether a published version used trusted publishing?
Open the version on npmjs.com. If you see a provenance badge linked to the workflow run + commit, OIDC + provenance worked. If not, it published anyway but bypassed trusted publishing.
How do I retry just the provenance step?
You cannot — provenance is generated during npm publish. To re-attest, you have to publish a new version. There is no separate “attest existing version” CLI flow today.
Does the workflow filename matter?
Yes. The trusted publisher you registered references a specific .github/workflows/<file>.yml. Renaming the file without updating the trusted publisher breaks the check.
Next step
The preflight checklist flags reasons 1, 2, 3, 4, 5, and 7 directly from a paste of your package.json and workflow. Reasons 6 and 8 are runtime conditions — they show up when you publish. Read the OIDC vs NPM_TOKEN migration for the staged switch, and the first-publish setup guide for the clean install.