ci(docs): switch dispatch trigger from GitHub App to fine-grained PAT (with skip-when-unconfigured guard)#1347
Merged
Conversation
Adds a job-level `if: vars.DOCS_DEPLOY_APP_ID != ''` guard to `docs-deploy-trigger.yml` so the workflow no-ops cleanly until the cutover steps in #1333 step 2 land (App creation + `DOCS_DEPLOY_APP_ID` var + `DOCS_DEPLOY_APP_KEY` secret). Pre-fix, every push to `docs/**` after #1339 merged ran the dispatch job, hit `actions/create-github-app-token@v1` with an empty `app-id` input, and exited red with `Input required and not supplied: app-id` (run 25646867498). Operators landing unrelated docs PRs would now see a stream of red runs that look like real CI failures but are actually the expected "cutover not done yet" state — exactly the friction the "Required repo configuration" comment block was meant to warn about. The job-level `if:` is the canonical "feature-flag a job" shape: unset repo variables resolve to empty string in expressions, so the guard is transparent once the var lands and the job runs every push as designed. Skipped runs show up as a grey "Skipped" badge in the Actions tab — the right visual signal for "intentionally inert until configured". Comment block above the workflow expanded to call this out explicitly so future readers don't try to "fix" the guard by removing it.
Replaces the `actions/create-github-app-token@v1` round-trip and the App-side `vars.DOCS_DEPLOY_APP_ID` + `secrets.DOCS_DEPLOY_APP_KEY` pair with a direct `secrets.DOCS_DEPLOY_PAT` (a fine-grained PAT scoped to `Actions: Read and write` on `sbpp.github.io` only). The previous fix in this PR's first commit (`0bf26262`) gated the job on `vars.DOCS_DEPLOY_APP_ID`. Renamed the gate variable to `vars.DOCS_DEPLOY_ENABLED` since "App ID" is no longer the right mental model — the variable is now a pure feature-flag the operator sets to opt in once the PAT is configured. (We can't reference `secrets.*` directly in a job-level `if:` per GitHub Actions' context-availability rules, so the paired VARIABLE is the canonical "feature-flag a job" shape.) Why PAT over App: a fine-grained PAT scoped to one repo + one permission carries the same blast radius as the org-owned App but takes ~2 minutes to provision instead of ~10. Tradeoff is the PAT is tied to a personal account and has a max one-year expiry that needs rotation; for a project of this size that's a fair deal. Operator instructions live in the workflow's top-of-file comment block + `docs/README.md`'s workflow table. The job-level `if:` guard pattern is unchanged in shape — every push to `docs/**` shows up as "Skipped" (grey badge) until the operator sets BOTH `secrets.DOCS_DEPLOY_PAT` and `vars.DOCS_DEPLOY_ENABLED`. The deploy shell in `sbpp.github.io` also has a `workflow_dispatch` trigger as a manual fallback while the PAT is pending.
The previous commit on this branch added a paired `vars.DOCS_DEPLOY_ENABLED` feature flag because I'd convinced myself `secrets.*` was unavailable in any `if:` context. That's half-true: it's unavailable in JOB-level `if:` (which is what I was trying to use), but it IS available in STEP-level `if:`. Since the trigger job has only one step (the dispatch), gating that single step on `if: secrets.DOCS_DEPLOY_PAT != ''` is behaviorally equivalent to a job-level guard — the runner spins up briefly to evaluate the step, sees no credential, marks the step Skipped, and the run finishes green-with-skipped instead of red-failing. The "wasted runner spin" cost I cited as justification for the variable is real but tiny (a few seconds of CI minutes vs ~30s of job-startup overhead either way), and the green-with-skipped status is arguably MORE informative than a fully-skipped grey job — it tells you "I ran, had no work to do" rather than "I didn't run". The variable was the operator's "I've done the cutover" signal, but the secret being set IS that signal. One source of truth, one less thing to forget. Cutover is now: create PAT, paste into `secrets.DOCS_DEPLOY_PAT`, done. Comment block above the workflow + `docs/README.md` workflow table updated.
3 tasks
Rushaway
pushed a commit
to srcdslab/sourcebans-pp
that referenced
this pull request
May 15, 2026
…in `if:` (sbpp#1350) sbpp#1347's skip-when-unconfigured guard used `if: secrets.DOCS_DEPLOY_PAT != ''` on the dispatch step. `secrets.*` isn't available in `if:` at any scope (workflow / job / step) per the Actions context-availability table — the parser rejects the file with "Unrecognized named-value: 'secrets'" before any job runs, so every push records a red workflow-file-issue run with no jobs (visible on the sbpp#1346 dependabot push, run 25652463088). Exactly the failure mode the guard was meant to prevent. Fix: read the secret into a precheck step's `env:` (where `secrets.*` IS allowed), shell-test for presence, emit a `configured=true|false` step output, gate the dispatch step on `steps.pat.outputs.configured == 'true'` (`steps.*` IS available in step `if:`). Operator-facing UX unchanged — dispatch step still shows as Skipped until the PAT is set, run is green. Both the file-level comment block and the inline step comment now spell out the actual context-availability rule so a future reader doesn't reach for the same broken shape.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Followup to #1339 (issue #1333 cutover).
What broke
Failing run: https://github.com/sbpp/sourcebans-pp/actions/runs/25646867498/job/75277366841
After #1339 merged, the first push to
mainunderdocs/**(the merge commit itself) fireddocs-deploy-trigger.yml, which immediately errored:vars.DOCS_DEPLOY_APP_IDresolved to empty string because the cutover step that creates the GitHub App and registers its credentials hadn't been done yet — and per discussion the App was overkill for a single-maintainer setup.What this PR changes
Two things:
Drop the GitHub App entirely; use a fine-grained PAT instead. The
actions/create-github-app-token@v1step is gone.gh api ...consumessecrets.DOCS_DEPLOY_PATdirectly viaGH_TOKEN. Credential setup goes from "create + install + register App" (~10+ minutes) to "create + paste fine-grained PAT" (~2 minutes).Step-level
if: secrets.DOCS_DEPLOY_PAT != ''guard so the dispatch is skipped cleanly when the credential isn't set yet. Until the operator pastes the PAT, every push todocs/**shows up as a green run with the dispatch step marked "Skipped", instead of red-failing on a missing credential. Once the PAT lands, the guard is transparent and the dispatch runs every push.The secret being set IS the operator's "I've done the cutover" signal; no separate feature-flag variable needed. (Earlier iteration of this PR used a paired
vars.DOCS_DEPLOY_ENABLEDbecause I'd misrememberedsecrets.*as unavailable in anyif:context — it's only unavailable at job level. Step-levelif:sees secrets fine.)Operator setup (one-time, when you want auto-deploy on PR merge)
This is now optional —
workflow_dispatch(manual button onsbpp.github.io's Actions tab) and the weekly cron in the deploy shell still cover the bases without any credential at all.sbpp(or your account ifsbppdoesn't show up — fine-grained PATs work either way)sbpp/sbpp.github.iosbpp/sourcebans-ppsettings → Secrets and variables → Actions → Secrets → New repository secret:DOCS_DEPLOY_PATThat's it. The dispatch starts firing on the next push to
docs/**.Classic PATs work too, but the fine-grained variant is strictly narrower (per-repo + per-permission scoping) and the right default.
Tradeoff vs the App
PATs are tied to a personal account and expire after at most a year. For a single-maintainer org this is acceptable; the rotation reminder is the only maintenance cost. If the project ever grows enough team members that a personal-account-tied credential becomes awkward, swapping back to the App is a small follow-up — just rebind the credential name in the workflow.
Validation
python3 -c 'import yaml; yaml.safe_load(open(".github/workflows/docs-deploy-trigger.yml"))'— YAML OK.docs/**should show "Skipped" on the dispatch step (green run overall) until you setDOCS_DEPLOY_PAT.Paired sibling PR
docs/README.md's workflow table is updated in this PR. The deploy workflow insbpp/sbpp.github.iomentions the App in its comment block; that's updated in sbpp/sbpp.github.io#55 (no behavior change there —repository_dispatchlistens for events from anywhere; the credential the dispatcher uses is invisible to the receiver).