mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #138] [MERGED] fix(bump-callers): fetch the roster at run time and mask it before the public log ever sees it (BE-6482) #148
Labels
No labels
cursor-review-catalog-drift
groom
groom
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/github-workflows#148
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/github-workflows/pull/138
Author: @mattmillerai
Created: 8/5/2026
Status: ✅ Merged
Merged: 8/17/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-6482-fetch-roster-at-runtime📝 Commits (4)
acd0c42fix(bump-callers): fetch the roster at run time and mask it before the log (BE-6482)4a2159ffix(bump-callers): split the roster read onto its own repo-scoped token, and harden the read's failure modes (BE-6482)17b4f0eMerge remote-tracking branch 'origin/main' into matt/be-6482-fetch-roster-at-runtimee4bd9cfMerge remote-tracking branch 'origin/main' into matt/be-6482-fetch-roster-at-runtime📄 Description
ELI-5
The bumper's list of which repos to bump is private. It used to be handed to the script through the workflow step's
env:block — and GitHub prints a step's env block into the run log before the step runs. This repo is public, so the private repo names showed up in a world-readable log a moment before the script's masking could hide them. Now the script goes and fetches the list itself and masks it before anything is printed, so there is nothing to print early.⚠️ DEPLOY PRECONDITION — needs an org admin before this works
The Cloud Code Bot GitHub App must be granted the repository permission Variables: read, and that permission update must be approved on the Comfy-Org installation. An agent cannot verify or grant this. Until it is granted, every
bump-*-callersrun fails with an explicit error naming exactly that grant:That is a loud, named failure rather than a silent no-op, on purpose.
What changed
.github/bump-callers/bump-callers.sh— the roster is now read at run time from the Actions variableVAR_NAMEnames, keyed onGITHUB_REPOSITORY, instead of arriving via the step env.CALLERS_JSONis genuinely unset ([ -z "${CALLERS_JSON+x}" ], not-z "$CALLERS_JSON"). A set-but-empty value still means "empty roster" — so the suite's empty-variable cases keep their meaning, and an operator's deliberateCALLERS_JSON=''can never fall through into bumping the live fleet.gh api …/actions/variables/<VAR>rather thangh variable get, because only the REST path surfaces the HTTP status. 404 →CALLERS_JSON="", which hands straight to the existingALLOW_EMPTYblock, so its semantics are bit-for-bit preserved (seeded-empty fleet → clean no-op; must-have-callers fleet → the loud clobber-detecting error). 403 → the Variables-grant error above. Anything else → an::error::carrying the sanitized reason only.::add-mask::loop remains the first place the roster's repo names are used, and carries a comment stating that invariant.All 8
bump-*-callers.ymlentrypoints —CALLERS_JSON:deleted from the stepenv:block;VAR_NAMEkept and its comment updated to say the script reads the roster from it.Docs — the "Known gap" blockquote in
.github/bump-callers/README.md, theKNOWN GAPheader paragraphs in the auto-label / pr-risk / detect-unreviewed-merge entrypoints, and the AGENTS.md line that referenced the gap. Thegh variable setupdate-flow instructions are unchanged.Tests — the
ghstub gained anapi repos/*/actions/variables/*handler (configurable roster fixture, 404 mode, 403 mode, 500 mode, and it logs every invocation). Seven new cases; every existing case is untouched.bash .github/bump-callers/tests/test_bump_callers.sh→ 207 passed, 0 failed;shellcheck -xclean on both files.Least privilege — kept, via a second token
The roster read needs a token that can read Actions variables, and a downscoped App token cannot: GitHub's
app-permissionsschema (the installation-token request body) has novariableskey, so there is nopermission-variablesinput to ask for. I verified that against the action's ownaction.ymlat both refs:permission-*inputsvariabd72941d(the pinned v1.12.0)The first cut resolved that by dropping
permission-contents/pull-requests/issuesfrom four entrypoints, widening the write token — the one that commits, branches, opens PRs and stamps labels across every caller repo — to full installation permissions. Every reviewer on the panel flagged it, and they were right: it is avoidable.Now there are two tokens. Each entrypoint mints a second
create-github-app-tokenwithrepositories: github-workflows, and the script uses it (VAR_TOKEN) for the roster read alone.GH_TOKENstays on its narrow trio, restored on all four entrypoints that had it. The read token is still un-downscoped — that part is forced — butrepositories:confines it to this one public repo rather than every repo the App is installed on.VAR_TOKENis optional: unset, the read falls back toGH_TOKEN, so a manual invocation still works. A test asserts the read runs underVAR_TOKENand not the write token, so collapsing back to one token fails CI.The action pin itself is untouched (v2+ breaks the cross-repo
owner:scoping the bumper depends on — see the in-file comment and the dependabot ignore);repositories:is confirmed present at the pinned ref.Read failure modes — hardened after review
ALLOW_EMPTYkeeps its exact semantics), but always logs a::warning::naming the variable and both scopes tried. A 404 is also how GitHub answers "this token cannot see the repository at all" — App uninstalled, repo-selection drift, a rename, a futureVAR_NAMEtypo — which would otherwise stop a wholeALLOW_EMPTY=truefleet from bumping in total silence.repos/…/actions/variables/…is narrower than the${{ vars.* }}binding it replaced, which also resolved org-level variables. The read now falls back toorgs/{owner}/…after a repo-scope 404, repo-level winning on a name clash. Best-effort: a failed org probe stays an empty roster, never a hard error.mktempis checked and the temp file is trapped onEXIT.::squash loops to a fixpoint — a singles|::|:|gpass is not one, since::::x::::collapses back into::x::. An adversarial-error-body case asserts no workflow command survives into the log.Judgment calls
Scope: 8 entrypoints, not 6. The ticket was written against the fleet as it stood then and named 6 entrypoints for the
CALLERS_JSONdeletion (anticipating pr-risk "if #119 has merged" — it has). Since thenbump-pr-risk-callers.yml(#119) andbump-detect-unreviewed-merge-callers.ymlhave landed. Applying the ticket's list literally would have left those two 403-ing on every run. I followed the ticket's stated intent ("EVERYbump-*-callers.ymlpresent at implementation time") over its enumerated line numbers. All 8 get theVAR_TOKENstep; the 4 that were downscoped stay downscoped.detect-unreviewed-merge's unseeded roster. That fleet is deliberately unseeded specifically because of this gap — its header and the README say to seed it "as the immediate follow-on to the masking fix". I updated both to say the blocker is now gone and to seed only after a dispatch is observed reading the variable without the 403, rather than seeding it myself (it needs private caller names I don't have, and it is an operator action either way). The run stays red until someone seeds it.
404 folds into "empty". As specified. Worth knowing the consequence: if GitHub ever answered a permissions failure with 404 instead of 403, a seeded-empty fleet would no-op silently. The must-have-callers fleets (cursor-review, auto-label, detect-unreviewed-merge) would still fail loudly, so the condition cannot go unnoticed fleet-wide.
Verify after merge (needs the App grant first)
workflow_dispatchbump-pr-size-callers (roster seeded empty,ALLOW_EMPTY=true): green, with noCALLERS_JSONin the step env dump. A 403 here means the Variables grant is still missing — grant it, then re-dispatch. A::warning::No PR_SIZE_CALLERS Actions variable found …instead means the variable is absent rather than seeded empty.workflow_dispatchbump-cursor-review-callers: env dump carries no roster, repo names appear only as***, and the caller bump PRs are still opened/updated.Note for the operator — the already-leaked rosters
This fix stops future leaks; it cannot retract past ones. The rosters already sit in pre-fix public run logs (e.g. run
30604087646, bump-cursor-review, 2026-07-31 — all 19CURSOR_REVIEW_CALLERSentries; run30604087518, auto-label, identically). GitHub's 90-day retention ages the July runs out around 2026-10-29;gh run deleteon the old bump runs removes them sooner. That is your call — out of scope here.Out of scope
Converting the roster variables to secrets (rejected in the ticket: the repo deliberately keeps them API-readable for ops/audit; it is the fallback only if the Variables grant is refused). Deleting historical runs.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.