Parent: none
Follow-up from: v0.2.0 release on 2026-04-17 where update-package-channels.yml failed to fire on release.published
Depends on: none (requires a new repo secret)
Decision (locked 2026-04-17)
Replace the default GITHUB_TOKEN used by release.yml's publish step with a fine-grained PAT (scope: contents:write) stored as RELEASE_PUBLISH_TOKEN. This restores the release.published event firing and re-enables automatic update-package-channels.yml runs (currently only triggerable manually via the workflow_dispatch escape hatch added in commit 1edd01d).
Why
GitHub's documented anti-recursion rule: events triggered by the default GITHUB_TOKEN do not spawn new workflow runs. release.yml's publish step uses the default token, so when v0.2.0 was published, update-package-channels.yml (on: release: types: [published]) never fired, leaving the Homebrew tap un-bumped until a manual operator intervention.
Until this lands, every release needs the operator to manually gh workflow run update-package-channels.yml -f tag=v<X> after the tag push — duplicative and error-prone.
Acceptance criteria
Concrete file pointers
Files to modify:
.github/workflows/release.yml — publish step(s) using GH_TOKEN (grep the file for the exact count before editing)
CLAUDE.md — Release operator secrets section, add RELEASE_PUBLISH_TOKEN entry
Files to read as reference — do NOT change:
.github/workflows/update-package-channels.yml — the downstream workflow that waits for the event (no code change there; the fix is upstream in release.yml)
CLAUDE.md existing HOMEBREW_TAP_TOKEN block — exact format template for the new RELEASE_PUBLISH_TOKEN entry
Test commands (literal, for agent self-check)
grep -c 'GITHUB_TOKEN' .github/workflows/release.yml — before + after, to confirm only the intended sites were substituted
- Scratch-tag probe:
gh tag v0.0.0-token-probe && gh push --tags → watch release.yml → watch update-package-channels.yml fire without manual dispatch → clean up: gh release delete v0.0.0-token-probe --yes && git tag -d v0.0.0-token-probe && git push origin :refs/tags/v0.0.0-token-probe
Out of scope (fenced)
- No changes to the
contents: write job-level permission in release.yml — the token scope change is sufficient; the YAML-level permission declaration is orthogonal
- No removal of
workflow_dispatch in update-package-channels.yml — it stays as a manual escape hatch even after automatic path works (for rare cases like backfilling a missed bump, or testing the formula rewrite logic without a real release)
- No new workflow file — this is strictly a token substitution + docs update
- No other secrets created — don't touch
HOMEBREW_TAP_TOKEN; each PAT stays scoped to one repo target
Related
Parent: none
Follow-up from: v0.2.0 release on 2026-04-17 where
update-package-channels.ymlfailed to fire onrelease.publishedDepends on: none (requires a new repo secret)
Decision (locked 2026-04-17)
Replace the default
GITHUB_TOKENused byrelease.yml's publish step with a fine-grained PAT (scope:contents:write) stored asRELEASE_PUBLISH_TOKEN. This restores therelease.publishedevent firing and re-enables automaticupdate-package-channels.ymlruns (currently only triggerable manually via theworkflow_dispatchescape hatch added in commit1edd01d).Why
GitHub's documented anti-recursion rule: events triggered by the default
GITHUB_TOKENdo not spawn new workflow runs.release.yml's publish step uses the default token, so when v0.2.0 was published,update-package-channels.yml(on: release: types: [published]) never fired, leaving the Homebrew tap un-bumped until a manual operator intervention.Until this lands, every release needs the operator to manually
gh workflow run update-package-channels.yml -f tag=v<X>after the tag push — duplicative and error-prone.Acceptance criteria
laradji/deadzoneonly, permissionContents: Read and write, no other permissionsRELEASE_PUBLISH_TOKEN(notGITHUB_TOKEN, which is reserved).github/workflows/release.yml— identify the step(s) performing the release publish (the job that runsgh release createorsoftprops/action-gh-releaseor equivalent) and replaceGH_TOKEN: ${{ secrets.GITHUB_TOKEN }}withGH_TOKEN: ${{ secrets.RELEASE_PUBLISH_TOKEN }}gh release uploadinvocations inrelease.ymlCLAUDE.md→ Release operator secrets section updated withRELEASE_PUBLISH_TOKENfollowing the same pattern asHOMEBREW_TAP_TOKEN(scope, purpose, rotation/renewal failure mode)release.ymlviaworkflow_dispatchpath against a scratch tagv0.0.0-token-probe, verifyupdate-package-channels.ymldoes fire on the resulting release publish, then delete the scratch release + tagworkflow_dispatchtrigger inupdate-package-channels.ymlstays (as operator escape hatch) — it does not get removed by this issueConcrete file pointers
Files to modify:
.github/workflows/release.yml— publish step(s) usingGH_TOKEN(grep the file for the exact count before editing)CLAUDE.md— Release operator secrets section, addRELEASE_PUBLISH_TOKENentryFiles to read as reference — do NOT change:
.github/workflows/update-package-channels.yml— the downstream workflow that waits for the event (no code change there; the fix is upstream in release.yml)CLAUDE.mdexistingHOMEBREW_TAP_TOKENblock — exact format template for the newRELEASE_PUBLISH_TOKENentryTest commands (literal, for agent self-check)
grep -c 'GITHUB_TOKEN' .github/workflows/release.yml— before + after, to confirm only the intended sites were substitutedgh tag v0.0.0-token-probe && gh push --tags→ watch release.yml → watch update-package-channels.yml fire without manual dispatch → clean up:gh release delete v0.0.0-token-probe --yes && git tag -d v0.0.0-token-probe && git push origin :refs/tags/v0.0.0-token-probeOut of scope (fenced)
contents: writejob-level permission in release.yml — the token scope change is sufficient; the YAML-level permission declaration is orthogonalworkflow_dispatchinupdate-package-channels.yml— it stays as a manual escape hatch even after automatic path works (for rare cases like backfilling a missed bump, or testing the formula rewrite logic without a real release)HOMEBREW_TAP_TOKEN; each PAT stays scoped to one repo targetRelated
1edd01d— added theworkflow_dispatchescape hatch that made the v0.2.0 tap bump possibleRelease operator secretssection — template for documenting the new secret