v0.50.2 — CI reads GitHub Packages with GITHUB_TOKEN, not a PAT
The frontend-module install step 403'd against npm.pkg.github.com:
403 Permission permission_denied: The token provided does not match expected scopes
The PKG_TOKEN PAT had been replaced with a fine-grained token, and GitHub Packages does not
accept fine-grained tokens at all — the npm registry documents classic PATs with read:packages as
the only supported personal-token type, so no permission selection on a fine-grained token could have
worked. Contents: read was the wrong axis regardless: package reads are not repository-contents
reads.
That is the immediate cause, but the PAT was the recurring one. The same secret took CI down in
v0.40.2 ("the PKG_TOKEN secret losing registry access"), and each failure needed a
manual rotation to clear.
GITHUB_TOKEN replaces it. It is minted per job and expires when the job ends, so it cannot be
left un-rotated, cannot be SSO-deauthorized, and — at the Docker build arg, where a token can persist
in image history — is worthless by the time anyone could read it out of a layer.
permissions: { contents: read, packages: read }at the workflow level, covering all four jobs. Declaring permissions replaces the defaults wholesale, which is whycontents: readis listed; nothing else is, since AWS auth uses static access keys rather than OIDC.- All five registry reads now use
secrets.GITHUB_TOKEN:pnpm installinlint-and-testandmigration-guard, bothinstall-hosted-modules-frontend.shsteps, anddocker build --build-arg NPM_TOKEN. scripts/install-hosted-modules-frontend.sh's failure hint was rewritten. It namedPKG_TOKENandread:packages, which is now the wrong advice for CI and would send the next person to rotate a secret that no longer exists. It now separates the two cases: in CI, a 403 means a package has not granted this repo Actions access, or the permission block is missing; locally, it means a fine-grained token that the registry will never accept.ciPackagesAuth.test.tsguards all of it —packages: readpresent,contents: readpresent, nowritein the block, every auth line onGITHUB_TOKEN, nosecrets.PKG_TOKENleft. It lives inserver-corerather than besideapps/web'sciProdBackup.test.tsbecauseapps/web'stestscript is a stub, so CI never runs that suite; a guard against a CI regression has to sit where CI executes. Each assertion was checked against a deliberately broken copy of the workflow.
The manual half, already done: each of the five packages in modules.hosted.json (vtt-assets,
system8, dnd5e, genai, scenes) must grant purple-cat-games/platform read access under its
Manage Actions access settings. GITHUB_TOKEN is scoped to this repository, so a package
published from another repo is invisible to it until listed there. A 403 after this release means a
package was missed, not that the token is wrong.
The PKG_TOKEN secret can now be deleted. Note that infra/gcp/cloudbuild.yaml still sources an
NPM_TOKEN from Secret Manager; that path is outside GitHub Actions and still needs a classic PAT if
it is ever used.