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
Log in to npmjs.com as cloudfour-user — the shared account, not a personal one. This matters: see the note at the bottom.
Avatar → Access Tokens → Generate New Token → Granular Access Token
Settings:
| Field | Value |
|---|
| Token name | cloudfour.com-patterns-ci |
| Description | GitHub Actions release workflow for cloudfour/cloudfour.com-patterns. Publishes @cloudfour/patterns via changesets. Stored as the NPM_TOKEN repo secret. |
| Expiration | Longest offered — then set a new reminder immediately |
| Packages and scopes | Read and write → Only select packages → @cloudfour/patterns |
| Organizations | No access |
| Allowed IP ranges | Leave blank (Actions runners have dynamic IPs) |
Copy the token — npm shows it once.
Update the secret:
gh secret set NPM_TOKEN --repo cloudfour/cloudfour.com-patterns
Revoke the old token in npm's Access Tokens list.
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.
The
NPM_TOKENrepository 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:
If you see that, the package hasn't vanished — the token is dead or lacks permission. Check the
NPM_TOKENsecret's age in Settings → Secrets and variables → Actions before looking anywhere else.How to renew
Log in to npmjs.com as
cloudfour-user— the shared account, not a personal one. This matters: see the note at the bottom.Avatar → Access Tokens → Generate New Token → Granular Access Token
Settings:
cloudfour.com-patterns-ciGitHub Actions release workflow for cloudfour/cloudfour.com-patterns. Publishes @cloudfour/patterns via changesets. Stored as the NPM_TOKEN repo secret.@cloudfour/patternsCopy the token — npm shows it once.
Update the secret:
gh secret set NPM_TOKEN --repo cloudfour/cloudfour.com-patternsRevoke the old token in npm's Access Tokens list.
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
@cloudfourscope 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-paradewas 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-userrather 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: writein the workflow plus a publisher configured on the npm package, andchangesets/actionv2 explicitly recommends it. Worth doing instead of the next renewal rather than in addition to it.