Skip to content
Latchkey

npm publish "403 Forbidden - You do not have permission to publish" in CI

npm authenticated your token but the registry rejected the publish: the account behind the token is not a maintainer of that package name, or the scope belongs to someone else. A 403 is an authorization failure, not a missing-login.

What this error means

npm publish stops with "npm ERR! code E403" and "403 Forbidden - PUT https://registry.npmjs.org/<package> - You do not have permission to publish '<package>'. Are you logged in as the correct user?"

npm
npm ERR! code E403
npm ERR! 403 403 Forbidden - PUT https://registry.npmjs.org/my-pkg - You do not have permission to publish "my-pkg". Are you logged in as the correct user?
npm ERR! 403 In most cases, you or one of your dependencies are requesting

Common causes

The token belongs to an account that is not a maintainer

The CI NODE_AUTH_TOKEN authenticates a user or automation token that was never added as a maintainer of the package, so the registry forbids the write.

The package name (or scope) is already owned by someone else

Publishing a new unscoped name that another account already registered returns 403, because you cannot publish into a name you do not own.

How to fix it

Confirm the token identity and maintainer list

  1. Run npm whoami in the job to confirm which account the token represents.
  2. Add that account as a maintainer with npm owner add <user> <package>, or use a token from an existing maintainer.
  3. For a brand-new name, publish under a scope you control (for example @yourorg/my-pkg).
Terminal
npm whoami
npm owner ls my-pkg

Set the auth token from a secret in the workflow

Use setup-node with a registry URL and pass an automation token via NODE_AUTH_TOKEN so the publish runs as a maintainer account.

.github/workflows/release.yml
- uses: actions/setup-node@v4
  with:
    node-version: 20
    registry-url: 'https://registry.npmjs.org'
- run: npm publish
  env:
    NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

How to prevent it

  • Publish under an npm scope your org owns so name ownership is unambiguous.
  • Use an automation token from a maintainer account, stored as a CI secret.
  • Keep the package maintainer list in sync with who holds the publishing token.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →