Summary
When registry-url is set, actions/setup-node always writes //<host>/:_authToken=${NODE_AUTH_TOKEN} to .npmrc. This is correct for the classic token-auth flow, but silently breaks the npm Trusted Publisher (OIDC) flow when no NODE_AUTH_TOKEN secret is configured (the intended state for OIDC).
With Trusted Publisher set up for the package and id-token: write granted, npm publish reads the .npmrc, sees _authToken= (empty after placeholder expansion), assumes "auth is configured", and never initiates the OIDC token exchange. It then fails with ENEEDAUTH or E404.
This is the documented and recommended way to publish via Trusted Publishers (npm docs example) — yet the workflow doesn't work out of the box.
Reproduction
name: Publish (broken)
on: workflow_dispatch
permissions:
id-token: write
contents: read
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- run: npm install -g npm@latest --force # ensure 11.5.1+
- run: npm publish --access public --provenance
# Fails with `npm error code ENEEDAUTH` even though:
# - Trusted Publisher is configured for the package on npmjs.com
# - id-token: write is granted
# - ACTIONS_ID_TOKEN_REQUEST_URL / TOKEN are set
# - npm 11.5.1+ is installed
Verified with npm@11.14.1 against @sofereditor/core@0.2.0 (PUT https://registry.npmjs.org/@sofereditor%2fcore → ENEEDAUTH).
Current workaround
Strip the line that the action wrote, after setup-node runs:
- run: |
npmrc="${NPM_CONFIG_USERCONFIG:-$HOME/.npmrc}"
sed -i '/_authToken/d' "$npmrc"
After that, npm publish detects ACTIONS_ID_TOKEN_REQUEST_* env vars and goes through the OIDC token exchange at /-/npm/v1/oidc/token/exchange/package/<pkg>. Same workflow then succeeds.
Proposed fix(es)
A few non-mutually-exclusive options, in increasing order of intervention:
-
New input auth-token-line: true|false (or write-auth-token-line) — when false, write only the registry= line. Default true to preserve back-compat. Lets users opt out cleanly:
- uses: actions/setup-node@v5
with:
registry-url: 'https://registry.npmjs.org'
auth-token-line: false # for Trusted Publisher OIDC
-
Auto-detect OIDC environment: if id-token permission is granted (ACTIONS_ID_TOKEN_REQUEST_TOKEN exists) AND no NPM_TOKEN/NODE_AUTH_TOKEN secret is wired in the calling step's env, skip the _authToken line by default. This needs a heuristic — perhaps "skip if ACTIONS_ID_TOKEN_REQUEST_TOKEN is set and NODE_AUTH_TOKEN is empty at action-run time". Risk: false positives if user wants classic auth and just hasn't passed the env yet.
-
Documentation update in the action README mentioning the interaction with Trusted Publishers, with a copy-pasteable workaround.
I'd be happy to PR (1) — minimal change, fully back-compat. Let me know which direction you prefer.
Cross-reference
Filed parallel issue at npm/documentation#1960 suggesting docs updates on the npm side.
Summary
When
registry-urlis set,actions/setup-nodealways writes//<host>/:_authToken=${NODE_AUTH_TOKEN}to.npmrc. This is correct for the classic token-auth flow, but silently breaks the npm Trusted Publisher (OIDC) flow when noNODE_AUTH_TOKENsecret is configured (the intended state for OIDC).With Trusted Publisher set up for the package and
id-token: writegranted,npm publishreads the.npmrc, sees_authToken=(empty after placeholder expansion), assumes "auth is configured", and never initiates the OIDC token exchange. It then fails withENEEDAUTHorE404.This is the documented and recommended way to publish via Trusted Publishers (npm docs example) — yet the workflow doesn't work out of the box.
Reproduction
Verified with
npm@11.14.1against@sofereditor/core@0.2.0(PUT https://registry.npmjs.org/@sofereditor%2fcore→ENEEDAUTH).Current workaround
Strip the line that the action wrote, after
setup-noderuns:After that,
npm publishdetectsACTIONS_ID_TOKEN_REQUEST_*env vars and goes through the OIDC token exchange at/-/npm/v1/oidc/token/exchange/package/<pkg>. Same workflow then succeeds.Proposed fix(es)
A few non-mutually-exclusive options, in increasing order of intervention:
New input
auth-token-line: true|false(orwrite-auth-token-line) — whenfalse, write only theregistry=line. Defaulttrueto preserve back-compat. Lets users opt out cleanly:Auto-detect OIDC environment: if
id-tokenpermission is granted (ACTIONS_ID_TOKEN_REQUEST_TOKENexists) AND noNPM_TOKEN/NODE_AUTH_TOKENsecret is wired in the calling step's env, skip the_authTokenline by default. This needs a heuristic — perhaps "skip ifACTIONS_ID_TOKEN_REQUEST_TOKENis set andNODE_AUTH_TOKENis empty at action-run time". Risk: false positives if user wants classic auth and just hasn't passed the env yet.Documentation update in the action README mentioning the interaction with Trusted Publishers, with a copy-pasteable workaround.
I'd be happy to PR (1) — minimal change, fully back-compat. Let me know which direction you prefer.
Cross-reference
Filed parallel issue at
npm/documentation#1960suggesting docs updates on the npm side.