Skip to content

Renew the npm release token before it expires (2026-11-23) #2452

Description

@spaceninja

The NPM_TOKEN repository secret is a 90-day npm granular access token created on 2026-08-25. It expires on or around 2026-11-23. When it does, the release workflow will fail at the publish step — the version commit and release PR will still work, so the failure only appears when a release is actually cut.

Worth renewing a week or two early rather than on the day. The exact expiry may be 2026-11-24 depending on whether npm counted from UTC, and there's nothing to gain from cutting it fine.

What failure looks like

npm reports an invalid or expired credential as a 404, not a 401, so the error is misleading:

E404: Not Found - PUT https://registry.npmjs.org/@cloudfour/patterns
The requested resource could not be found or you do not have permission to access it.

If you see that, the package hasn't vanished — the token is dead or lacks permission. Check the NPM_TOKEN secret's age in Settings → Secrets and variables → Actions before looking anywhere else.

How to renew

  1. Log in to npmjs.com as cloudfour-user — the shared account, not a personal one. This matters: see the note at the bottom.

  2. Avatar → Access TokensGenerate New TokenGranular Access Token

  3. Settings:

    FieldValue
    Token namecloudfour.com-patterns-ci
    DescriptionGitHub Actions release workflow for cloudfour/cloudfour.com-patterns. Publishes @cloudfour/patterns via changesets. Stored as the NPM_TOKEN repo secret.
    ExpirationLongest offered — then set a new reminder immediately
    Packages and scopesRead and writeOnly select packages@cloudfour/patterns
    OrganizationsNo access
    Allowed IP rangesLeave blank (Actions runners have dynamic IPs)
  4. Copy the token — npm shows it once.

  5. Update the secret:

    gh secret set NPM_TOKEN --repo cloudfour/cloudfour.com-patterns
  6. Revoke the old token in npm's Access Tokens list.

  7. Open a new issue like this one with the next expiry date, or set a calendar reminder.

Why "Only select packages" and "No access" for orgs

Organization permission governs managing the org itself — members, teams, settings — which publishing doesn't need. And scoping to the single package rather than the whole @cloudfour scope means a leaked CI token can't push a malicious @cloudfour/eslint-config. This repo publishes exactly one package, so there's no cost to the tighter scope.

Why the shared account

This token replaced one created in 2020 that had almost certainly expired. The same evening, the sibling repo cloudfour/lighthouse-parade was blocked from publishing entirely because its npm package is owned solely by a former contributor's personal account — see cloudfour/lighthouse-parade#390. Nobody noticed for six years, because a publish credential only fails when you try to use it.

Minting this from cloudfour-user rather than an individual keeps that from happening here.

The longer-term fix

Renewing every 90 days is a chore that will eventually be forgotten — that's the failure mode this issue exists to prevent, and it's not a strong control.

npm's trusted publishing removes the token entirely: GitHub Actions authenticates via OIDC, so there's nothing to expire, rotate, or leak. It needs id-token: write in the workflow plus a publisher configured on the npm package, and changesets/action v2 explicitly recommends it. Worth doing instead of the next renewal rather than in addition to it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    👷 environmentSetup, build tools, configuration, etc.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions