GitHub Actions: npm publish fails with "ENEEDAUTH"
With no npm token in the job, npm publish relies on trusted publishing: npm asks GitHub for an OIDC token and trades it at the registry for a short-lived publish token. If any part of that fails, npm does not say so at the default log level. It goes on without credentials and stops at "need auth". The error is the symptom; the reason is in a log line you have to ask for.
What it looks like
npm error code ENEEDAUTH npm error need auth This command requires you to be logged in to https://registry.npmjs.org/ npm error need auth You need to authorize this machine using `npm adduser`
- If the job also has a token, npm falls back to it. A token that cannot publish the package gets
404 Not Found - PUT https://registry.npmjs.org/<package> - Not foundinstead. One such token is the placeholderXXXXX-XXXXX-XXXXX-XXXXXthatactions/setup-nodebefore v7 put inNODE_AUTH_TOKENwhenregistry-urlwas set (npm/cli #9969, setup-node #1558). - More workflows are moving to trusted publishing because npm granular access tokens that bypass 2FA are losing direct publishing; GitHub's changelog of July 31, 2026 gives January 2027 as the target.
Make npm print the reason
npm's lib/utils/oidc.js never fails the command. Each way it can give up is one log line at verbose or silly. Run the publish once with:
- run: npm publish --loglevel silly
and look for the lines with oidc:
npm silly oidc Skipped because incorrect permissions for id-token within GitHub workflow: the job has noid-token: write(2).npm verbose oidc Failed token exchange request with body message: ...: GitHub gave a token and the registry refused it. The message is the registry's reason, for example "OIDC token exchange error - package not found" (npm/cli #9969). Usually the trusted publisher does not match the run (3).npm verbose oidc Successfully retrieved and set token: trusted publishing worked, and the failure is something else.- No
oidcline at all: this npm has no trusted publishing (1).
Several projects added --loglevel verbose for this after a refused exchange (duskfleet #314, agentboard #71). verbose shows the registry's reason but not the missing permission, which is only at silly.
1. npm is older than 11.5.1
npm's documentation: trusted publishing "requires npm CLI version 11.5.1 or later and Node version 22.14.0 or higher." The Ubuntu 24.04 runner image comes with Node.js 22.23.4 and npm 10.9.9, and node-version: '22' in setup-node also gives npm 10. A job that does not set up Node 24 publishes with an npm that never tries. In homeboy's release action, Node setup was skipped for components that declared no Node version, and those publishes failed with ENEEDAUTH (#17247).
- uses: actions/setup-node@v7
with:
node-version: '24'
- run: npm --version
- run: npm publish
Or keep the Node version and run npm install -g npm@latest before publishing.
2. No id-token: write
Without it, GitHub gives the job no way to ask for an OIDC token. A permissions: block on the job replaces the workflow's, so the publish job needs it in its own block if it has one. When the publish runs in a reusable workflow, npm's documentation says the permission "must also be given to both parent and child workflows."
permissions: contents: read id-token: write
3. The trusted publisher does not match the run
npm checks the run against what you entered in the package's settings on npmjs.com, and it does not check the entry when you save it. All fields are case-sensitive.
- Workflow filename is the file name only, with
.ymlor.yaml. For a reusable workflow, npm's documentation says validation uses the calling workflow's name, not the file that containsnpm publish. - Environment name has to match the publish job's
environment:; leave it empty if the job has none. astria's notes say the exchange is refused when the environment differs or is missing. - Every package needs its own trusted publisher, set in that package's settings. In astria's release, four packages published and three that had none stopped at ENEEDAUTH (#140).
repository.urlinpackage.jsonmust match the GitHub repository exactly, which also catches forks.- A new trusted publisher must complete its first successful publish within 2 days, or it expires and can no longer be used or edited. Delete it and add a new one. The same goes for changing one: entries cannot be edited, only deleted and added again.
4. A self-hosted runner
npm's documentation: "Self-hosted runners are not currently supported but are planned for future releases." Publish from a GitHub-hosted runner, or use a token there.
Telling it apart
402 Payment Requiredon a scoped package: it is being published as private. Add--access publicor"publishConfig": {"access": "public"}.403"You cannot publish over the previously published versions": the version inpackage.jsonis already on npm.EOTP: the token is one that needs a one-time password. Trusted publishing does not.npm cifails on a private dependency: trusted publishing is only for publishing. Installing private packages still needs a read-only token.
See failed jobs from IntelliJ IDEA
Pipeline Inbox for GitHub Actions Workflows, a plugin by Lesstep for JetBrains IDEs 2024.1 or later, lists the runs of all your repositories in an IDE tool window, with their jobs and steps, and your current branch's runs are in the status bar. Reading is free; re-running failed jobs, cancelling, running workflows and reviewing deployments are Pro (US$1.50 a month, with a 30-day trial).
GitHub and GitHub Actions are trademarks of GitHub, Inc.; npm is a trademark of npm, Inc.; IntelliJ IDEA is a trademark of JetBrains s.r.o. This page is not affiliated with, sponsored or endorsed by GitHub, Inc., npm, Inc. or JetBrains.
More: AWS: "Not authorized to perform sts:AssumeRoleWithWebIdentity" · "Node.js 20 is deprecated" · "Input required and not supplied" · GitHub Actions plugins for IntelliJ IDEA compared · 日本語