release-bot should use a PAT instead of system token so rolling-PR pushes trigger downstream CI #257

Closed
opened 2026-06-29 12:24:36 +02:00 by quartermaster · 3 comments

Empirical anchor

tmux-tell's first-ever release-toolkit-driven cut (tmux-tell#630, v0.23.0) hit a substrate-gap on 2026-06-29: release-bot pushed chore(release): prepare v0.23.0 at SHA 43cb30 to release-prep/rolling, but no CI ran on that SHA — the required check test / lint + build + test (pull_request) stays unsatisfied → PR can't merge.

Root cause

Release-bot pushes use the system token (the equivalent of GitHub Actions' GITHUB_TOKEN). Forgejo Actions follows the GitHub-style anti-recursion safeguard: workflows triggered by system-token pushes are suppressed to avoid infinite loops (release-bot push → workflow runs → workflow pushes → workflow runs → ...).

This is a documented Forgejo Actions behavior and intentional at the platform level. The consequence for release-toolkit: every rolling-PR update via release-bot leaves the consumer's required CI checks unsatisfied, blocking merge.

Verified empirically:

  • Last test.yml workflow run on tmux-tell was at SHA 441f39 (a quartermaster-identity push) — fired.
  • Next push to release-prep/rolling at SHA 43cb30 was via release-bot — did NOT fire any workflow runs.

Mitigation paths

Short-term (already shipped on consumer)

tmux-tell PR #TBD adds workflow_dispatch: to test.yml so operators can manually re-run CI from the Forgejo Actions UI when this class fires. Mitigation, not fix.

Long-term (this tracker)

Release-toolkit's release-bot should push using a Personal Access Token (PAT) instead of the system token. PAT-authored pushes are treated as user pushes by Forgejo Actions and DO trigger downstream workflows.

Implementation surface:

  1. release-toolkit reusable workflows that perform the release-bot push (reusable-release.yml likely, or wherever the chore(release): prepare vX.Y.Z commit gets created + pushed) should authenticate using a PAT secret instead of secrets.GITHUB_TOKEN / system token
  2. Document a new consumer-side secret requirement: e.g. RELEASE_BOT_PAT with repo:write scope, owned by a non-system user (the existing release-bot Forgejo user works perfectly)
  3. Update docs/integration.md adopter walkthrough to include the PAT setup step
  4. Migration note for existing adopters (cellblock + tmux-tell): generate PAT under release-bot account, add to repo secrets, no other code changes needed

Alternative: workflow-level token override

Forgejo Actions may support permissions: or token overrides at the workflow level to allow specific triggers. Worth surveying that surface before committing to PAT — it might be a cleaner fix (no new secret).

Composition

  • Companion to consumer-side workflow_dispatch short-term mitigation (every adopter that hits the class can short-term-fix with that, long-term-fix with this)
  • This is exactly the class of substrate-survey-before-design lesson the cross-chamber discipline thread has been catching: "the push DID happen ≠ the workflow WILL run." The push-happened invariant is necessary but not sufficient.

What this issue does NOT do

  • Does NOT propose changing Forgejo's anti-recursion safeguard (that's intentional platform behavior we work around)
  • Does NOT fix tmux-tell#630 directly (consumer-side workflow_dispatch is the short-term unblock)
  • Does NOT touch consumer repos beyond the documented PAT setup step

Refs

  • tmux-tell#630 (empirical anchor — first-ever toolkit cut on a real consumer hit this)
  • Consumer-side workflow_dispatch mitigation PR (tmux-tell, TBD)
  • Forgejo Actions docs on the GITHUB_TOKEN-equivalent push-doesn't-trigger-workflows behavior

priority/medium · size/M (substantive but bounded — secret-handling + reusable workflow update + docs)

## Empirical anchor tmux-tell's first-ever release-toolkit-driven cut (tmux-tell#630, v0.23.0) hit a substrate-gap on 2026-06-29: release-bot pushed `chore(release): prepare v0.23.0` at SHA 43cb30 to `release-prep/rolling`, but no CI ran on that SHA — the required check `test / lint + build + test (pull_request)` stays unsatisfied → PR can't merge. ## Root cause Release-bot pushes use the **system token** (the equivalent of GitHub Actions' `GITHUB_TOKEN`). Forgejo Actions follows the GitHub-style **anti-recursion safeguard**: workflows triggered by system-token pushes are suppressed to avoid infinite loops (release-bot push → workflow runs → workflow pushes → workflow runs → ...). This is a documented Forgejo Actions behavior and intentional at the platform level. The consequence for release-toolkit: every rolling-PR update via release-bot leaves the consumer's required CI checks unsatisfied, blocking merge. Verified empirically: - Last `test.yml` workflow run on tmux-tell was at SHA 441f39 (a `quartermaster`-identity push) — fired. - Next push to release-prep/rolling at SHA 43cb30 was via `release-bot` — did NOT fire any workflow runs. ## Mitigation paths ### Short-term (already shipped on consumer) tmux-tell PR #TBD adds `workflow_dispatch:` to test.yml so operators can manually re-run CI from the Forgejo Actions UI when this class fires. Mitigation, not fix. ### Long-term (this tracker) Release-toolkit's release-bot should push using a **Personal Access Token (PAT)** instead of the system token. PAT-authored pushes are treated as user pushes by Forgejo Actions and DO trigger downstream workflows. **Implementation surface**: 1. release-toolkit reusable workflows that perform the release-bot push (`reusable-release.yml` likely, or wherever the `chore(release): prepare vX.Y.Z` commit gets created + pushed) should authenticate using a PAT secret instead of `secrets.GITHUB_TOKEN` / system token 2. Document a new consumer-side secret requirement: e.g. `RELEASE_BOT_PAT` with `repo:write` scope, owned by a non-system user (the existing `release-bot` Forgejo user works perfectly) 3. Update `docs/integration.md` adopter walkthrough to include the PAT setup step 4. Migration note for existing adopters (cellblock + tmux-tell): generate PAT under release-bot account, add to repo secrets, no other code changes needed ### Alternative: workflow-level token override Forgejo Actions may support `permissions:` or token overrides at the workflow level to allow specific triggers. Worth surveying that surface before committing to PAT — it might be a cleaner fix (no new secret). ## Composition - Companion to consumer-side `workflow_dispatch` short-term mitigation (every adopter that hits the class can short-term-fix with that, long-term-fix with this) - This is exactly the class of substrate-survey-before-design lesson the cross-chamber discipline thread has been catching: "the push DID happen ≠ the workflow WILL run." The push-happened invariant is necessary but not sufficient. ## What this issue does NOT do - Does NOT propose changing Forgejo's anti-recursion safeguard (that's intentional platform behavior we work around) - Does NOT fix tmux-tell#630 directly (consumer-side workflow_dispatch is the short-term unblock) - Does NOT touch consumer repos beyond the documented PAT setup step ## Refs - tmux-tell#630 (empirical anchor — first-ever toolkit cut on a real consumer hit this) - Consumer-side workflow_dispatch mitigation PR (tmux-tell, TBD) - Forgejo Actions docs on the GITHUB_TOKEN-equivalent push-doesn't-trigger-workflows behavior priority/medium · size/M (substantive but bounded — secret-handling + reusable workflow update + docs)
Author
Owner

Sub-finding: release-bot identity needs push_whitelist_usernames membership, not just a PAT

Surfaced during the tmux-tell#630 recovery (release-toolkit#259's sibling investigation): the toolkit's path-α design assumes release-bot can direct-push manifest commits to main past branch protection. That requires the release-bot account to be in the consumer repo's push_whitelist_usernames for main.

Empirical anchor: tmux-tell's main branch protection has push_whitelist_usernames: ["alex", "quartermaster"] — release-bot is NOT in the list. When Engineer's #635 recovery runbook attempted Step 3 (manifest push as release-bot identity), the push got rejected with Forgejo: Not allowed to push to protected branch main. Workaround for the recovery: push from a whitelisted user (quartermaster) with author=release-bot. But in the normal automated cut flow, the workflow's release-bot push would have hit the same rejection if the cut had fired properly.

Same root as the original #257 framing: "toolkit assumes a release-bot identity provisioned with push + CI-trigger rights the adopter must actually grant." The PAT (this issue's primary scope) covers the CI-trigger half; the push_whitelist membership covers the branch-protection-bypass half. Both are adopter-setup responsibilities the toolkit currently doesn't surface explicitly.

Suggested scope extension for this tracker:

  1. Document the adopter precondition in docs/integration.md: "release-bot user (or whatever identity the toolkit's pushes use) must be added to the consumer repo's branch protection push_whitelist_usernames for the default branch."
  2. Pre-flight check (optional): in reusable-release.yml, probe the consumer repo's branch protection config + fail-loud at workflow start if the configured release-bot identity isn't in the whitelist. Same shape as #124's structural backstop for self-bootstrap re-pin.
  3. PAT migration note (this issue's primary scope): when the PAT switch lands, the docs update should bundle the push_whitelist requirement into the same "release-bot setup" section so adopters see both preconditions at once.

The two gaps compose well: PAT enables CI-trigger via PR/synchronize events; whitelist membership enables the direct-push manifest commits. An adopter who provisions one but not the other would see asymmetric failures (cuts firing but manifest stuck, OR manifests pushing but CI not triggering).

Refs:

  • tmux-tell#630 (the first-cut empirical anchor)
  • tmux-tell#635 (the recovery runbook + the rejection observation in Step 3)
  • release-toolkit#259 (Layer-1 cut detection — sibling gap from the same first-cut)

This brings the today-surfaced gap count from #630 to 6:

  1. release-toolkit#257 (this issue) — release-bot PAT for CI-trigger
  2. tmux-tell#632 — workflow_dispatch escape hatch (merged)
  3. tmux-tell#633 — check-changelog-placement skip regex (merged)
  4. release-toolkit#258 — fragment-vs-commit generation-layer dedup
  5. release-toolkit#259 — Layer-1-HEAD-only cut detection
  6. THIS sub-finding — release-bot push_whitelist requirement
## Sub-finding: release-bot identity needs `push_whitelist_usernames` membership, not just a PAT Surfaced during the tmux-tell#630 recovery (release-toolkit#259's sibling investigation): the toolkit's path-α design assumes release-bot can direct-push manifest commits to `main` past branch protection. That requires the release-bot account to be in the consumer repo's `push_whitelist_usernames` for `main`. **Empirical anchor**: tmux-tell's `main` branch protection has `push_whitelist_usernames: ["alex", "quartermaster"]` — release-bot is NOT in the list. When Engineer's #635 recovery runbook attempted Step 3 (manifest push as release-bot identity), the push got rejected with `Forgejo: Not allowed to push to protected branch main`. Workaround for the recovery: push from a whitelisted user (quartermaster) with author=release-bot. But in the normal automated cut flow, the workflow's release-bot push would have hit the same rejection if the cut had fired properly. **Same root as the original #257 framing**: "toolkit assumes a release-bot identity provisioned with push + CI-trigger rights the adopter must actually grant." The PAT (this issue's primary scope) covers the CI-trigger half; the push_whitelist membership covers the branch-protection-bypass half. Both are adopter-setup responsibilities the toolkit currently doesn't surface explicitly. **Suggested scope extension for this tracker**: 1. **Document the adopter precondition** in `docs/integration.md`: "release-bot user (or whatever identity the toolkit's pushes use) must be added to the consumer repo's branch protection `push_whitelist_usernames` for the default branch." 2. **Pre-flight check** (optional): in `reusable-release.yml`, probe the consumer repo's branch protection config + fail-loud at workflow start if the configured release-bot identity isn't in the whitelist. Same shape as #124's structural backstop for self-bootstrap re-pin. 3. **PAT migration note** (this issue's primary scope): when the PAT switch lands, the docs update should bundle the push_whitelist requirement into the same "release-bot setup" section so adopters see both preconditions at once. The two gaps compose well: PAT enables CI-trigger via PR/synchronize events; whitelist membership enables the direct-push manifest commits. An adopter who provisions one but not the other would see asymmetric failures (cuts firing but manifest stuck, OR manifests pushing but CI not triggering). **Refs**: - tmux-tell#630 (the first-cut empirical anchor) - tmux-tell#635 (the recovery runbook + the rejection observation in Step 3) - release-toolkit#259 (Layer-1 cut detection — sibling gap from the same first-cut) This brings the today-surfaced gap count from #630 to **6**: 1. **release-toolkit#257** (this issue) — release-bot PAT for CI-trigger 2. tmux-tell#632 — workflow_dispatch escape hatch (merged) 3. tmux-tell#633 — check-changelog-placement skip regex (merged) 4. release-toolkit#258 — fragment-vs-commit generation-layer dedup 5. release-toolkit#259 — Layer-1-HEAD-only cut detection 6. **THIS sub-finding** — release-bot push_whitelist requirement
Author
Owner

Follow-up to 76476 — on operator review the push_whitelist sub-finding has been split out into its own tracker: release-toolkit#260.

The two gaps share root framing ("release-bot identity needs rights the adopter must grant") but are mechanically distinct: #257 is HTTP-token-auth (fixes CI-trigger), #260 is Forgejo branch-protection-allowlist (fixes direct-push). Separate fix paths, separate verification, asymmetric failure modes. The original folded-comment text stays here as cross-ref context; #260 is the dedicated home for the whitelist work going forward.

Follow-up to 76476 — on operator review the push_whitelist sub-finding has been split out into its own tracker: **release-toolkit#260**. The two gaps share root framing ("release-bot identity needs rights the adopter must grant") but are mechanically distinct: #257 is HTTP-token-auth (fixes CI-trigger), #260 is Forgejo branch-protection-allowlist (fixes direct-push). Separate fix paths, separate verification, asymmetric failure modes. The original folded-comment text stays here as cross-ref context; #260 is the dedicated home for the whitelist work going forward.
Owner

Closed by PR #263 merge at 9075ff3 (rebased combined head). Bosun manually closing since Do=rebase preserved head commit message (close-keyword not in commit body). All ACs verified clean: 4 #257 bats + 8 dedup bats + release-prep.bats 34/0 on combined-tree per Surveyor 3277 re-pin (verified #262 dedup-wiring filename-keyed version + #257 push hunk byte-identical via API diff).

Closed by PR #263 merge at 9075ff3 (rebased combined head). Bosun manually closing since Do=rebase preserved head commit message (close-keyword not in commit body). All ACs verified clean: 4 #257 bats + 8 dedup bats + release-prep.bats 34/0 on combined-tree per Surveyor 3277 re-pin (verified #262 dedup-wiring filename-keyed version + #257 push hunk byte-identical via API diff).
bosun closed this issue 2026-06-29 21:22:52 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#257
No description provided.