mirror of
https://github.com/Comfy-Org/github-workflows.git
synced 2026-11-05 00:17:37 +00:00
[PR #235] [MERGED] feat(bump-callers): honor a Skip-caller-bump commit trailer in the shared preflight #241
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#241
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/235
Author: @mattmillerai
Created: 8/27/2026
Status: ✅ Merged
Merged: 8/27/2026
Merged by: @mattmillerai
Base:
main← Head:matt/be-9571-skip-caller-bump-trailer📝 Commits (4)
cf39668feat(bump-callers): honor a Skip-caller-bump commit trailer in the shared preflightb238b40fix(bump-callers): close the review findings on the Skip-caller-bump gatec4ef875fix(bump-callers): close round-2 review findings on the Skip-caller-bump gatee03124cfix(bump-callers): never read a trailer out of the title paragraph (BE-9571)📊 Changes
4 files changed (+940 additions, -10 deletions)
View changed files
📝
.github/bump-callers/README.md(+119 -0)📝
.github/bump-callers/preflight.sh(+334 -10)📝
.github/bump-callers/tests/test_preflight.sh(+482 -0)📝
AGENTS.md(+5 -0)📄 Description
ELI-5
When someone edits a workflow file here — even just to reword a comment — a robot opens a "please update your copy" PR in every repo that uses that workflow. That's the right thing for real changes, but for a comment reword it's pure busywork: nothing a consumer runs actually changed, yet several repos each get a PR a human has to review. This change adds a magic phrase (
Skip-caller-bump: true) an author can put at the bottom of a commit message to say "this change doesn't matter to consumers, don't send those PRs." The robot only honors it when it's completely sure it's safe: only for automatic push-triggered runs, only when the phrase is in the message's real sign-off block, only when every commit in the push carries it, and — because another run may be quietly waiting for this one to do the work — only when nothing else is left unsent. If anything at all looks off it just sends the PRs like before: being noisy is annoying, but skipping a real update would be a bug.What changed
preflight.sh— the ONE shared staleness/decommission guard everybump-*-callers.ymlentrypoint runs ahead of the token mint — gains a trailer gate as its final check, inserted immediately before the finalemit trueso it runs on both the normal and the re-pointed path and every existing loud validation/staleness/decommission verdict keeps precedence (the trailer can never mask an error or convert another verdict). No entrypoint and nobump-callers.shchange: preflight reads$GITHUB_EVENT_NAME/$GITHUB_EVENT_PATH, which Actions sets for every step, so all 13 fleets get the gate through the shared script with zero wiring.It skips (
proceed=false,new_shaas computed, exit 0) ONLY when all of: the event is exactlypush(aworkflow_dispatchrun always bumps — it is the fleets' documented recovery path and the manual override after a mistaken trailer); the payload is a readable file jq parses;.commitshas 1–2047 entries (GitHub documents the push webhook payload — which is what$GITHUB_EVENT_PATHholds — as carrying a maximum of 2048 commits, so at 2048 the array may be truncated and an incompletely-checked push must not skip; the far lower 20-entry cap belongs to the Events API'sPushEvent, a different representation, and the webhook payload carries no.sizeto compare a length against); and EVERY commit's message carries a line matching^[Ss]kip-[Cc]aller-[Bb]ump:[[:space:]]*true[[:space:]]*$in its trailing trailer block. Any other condition, including any read/parse failure or a missingjq, falls through to bumping: the gate fails open, so its worst possible bug is status-quo churn, never pin drift. On a skip it logs a::notice::naming the head SHA, the commit count, the all-commits fact, and both recovery paths — and, on the re-pointed path, naming the earlier "pinning callers to … and proceeding" line it overrides, so the log never ends on two contradictory statements. Messages come from the event payload, notgit log— the default checkout isfetch-depth: 1, so the push range's parents are not present locally.Trailer-block, not body-wide. A trailer counts only inside the maximal suffix of lines that are blank or
Token: valueshaped that also starts a paragraph (a blank line precedes it, or it is the whole message) —git interpret-trailers --parsesemantics. A body-wide line match reads the token as a declaration when it is only being quoted: agit cherry-pick -xcopy (whose appended(cherry picked from commit …)line is not trailer-shaped, so it correctly ends the block), a reapply carrying the original body, or a commit whose prose documents this very feature at column 0 — each of those a behavioral commit silently suppressing its own bump. The paragraph-start half is what covers the quote that is the paragraph's last line (A churn commit ends with:/Skip-caller-bump: true), which the suffix rule alone accepts because the scan stops on the prose above having already taken the token. Blank lines stay legal inside the block, so GitHub's appendedCo-authored-by:paragraph cannot push a valid trailer out of it — and it cannot launder such a quote either, since the scan walks back through it to the same prose.This also settles what the all-commits rule is and isn't good for. On a direct push to
mainit stops a mixed push — one behavioral commit, one trailered docs commit — from skipping. It is not what guards the shape changes actually arrive in: this repo squash-merges withsquash_merge_commit_message: COMMIT_MESSAGES(verified against the repo settings API), so a whole PR lands as ONE commit and the all-commits rule is then trivially the head-commit rule. What guards that path is the trailer-block requirement plus review — GitHub's squashed body concatenates the branch commits in order (only each first line prefixed*; verified against this repo's own squashed history), so only a trailer in the LAST commit's message reaches the squashed trailer block, which is also the text the squash-merge UI shows the person pressing the button. A trailer that reachesmaintherefore claims the WHOLE PR is bump-irrelevant, for EVERY fleet, and reviewers treat it like any other reviewed line.The hand-off guard. The staleness skip earlier in the same file is sound only because of what its own message claims — the newer commit "has its own run, which will pin the newer content". Once a run can decline on a trailer, that hand-off breaks: behavioral commit A lands on a watched surface, trailered churn commit B rewords a comment in the same surface moments later, A's run defers to B's run, B's run skips, and A reaches no caller until some later behavioral commit happens along — exactly the pin drift the gate's header promised could not happen. So before deferring, the staleness branch now asks whether EVERY commit
maingained since this one that touches the watched surface is trailered; if so it does not defer — it pins the verified tip and proceeds, being the last run that will pin this content. Restricted to the watched surface because only a commit matching the fleet'spaths:filter starts a run at all: an unwatched commit can neither decline nor pin, so counting it would defer this run to a run that was never triggered. The pathspecs are whichever shape the staleness comparison itself used (WATCHED_PATHSPECS, elseWATCHED+WATCHED_ASSETS), so the two mean the same thing by construction. Both endpoints are observable from inside the run, so this needs no new state; it lives in the branch that has already--unshallowed for its ancestry checks, so the history is present. The walk is onegit log -n <bound+1>plus one jq pass, bounded at the same 2047 as the gate's.commitsbound so no range the gate would skip commit-by-commit is one the guard declines to evaluate. A range it cannot read (nojq, a failed walk, nothing selected, past that bound) leaves the pre-existing stale verdict standing — the guard narrows that skip, and an inability to check is not grounds to widen it — but logs a distinct line first naming the hand-off as UNVERIFIED, so a fleet that quietly stopped bumping leaves a trace of which of the two happened. The trailer definition itself lives in ONE shared jq function read by both consumers rather than being copied.The empirical motivator is commit
eaee6df(a comment-reword commit, merged via #199) which fanned four fleets at once; the mechanism was deferred from the review thread on #226.Docs: preflight.sh's header documents the trailer contract (trailer-block rule, exact regex, all-commits rule, truncation guard, fail-open posture, dispatch override, hand-off guard, known limit); the bump-callers README gains a "Suppressing a churn bump" section; AGENTS.md gains one bullet telling authors where to write the trailer and reviewers when to reject it.
Tests:
test_preflight.shis at 389 passed / 0 failed. On top of the original nine cases — trailered pushes skipping with the notice, untrailered and mixed pushes bumping, the truncation boundary,workflow_dispatchalways bumping, missing/malformed/unreadable payloads failing open with exit 0, the exact-regex line variants, stale and decommission verdicts winning over the trailer, a loud validation error staying loud, and the gate applying on the re-point path — there are now cases for the hand-off guard on both the full and the shallow checkout shape (plus its negative: one untrailered commit anywhere in the range and the stale verdict stands), three in-block accepts and three out-of-block rejects for trailer-block parsing, the 2047/2048 boundary with a 25-commit push that the old bound would have wrongly bumped, and an assertion that the re-point override sentence names the actual tip SHA. Round 2 adds: two paragraph-boundary rejections;trailer_handoff_unwatchedandtrailer_handoff_unwatched_pathspecs(an untrailered commit touching only unwatched — or:(exclude)-ed — paths must not make the guard defer);trailer_handoff_unknown, which drives a failingjqonPATHand asserts the distinct UNVERIFIED line, then re-runs the same range with a workingjqto prove the line reports the guard's inability and not the range;trailer_bounds, a contract check that the guard's bound and the gate's.commitsbound are one number; and an assertion that the hand-off path no longer prints the shared re-point echo's contradictory "the watched surface is unchanged" line.run_preflightalso blanksGITHUB_EVENT_NAME/GITHUB_EVENT_PATHby default (cases opt in with their own values) — the suite itself runs inside Actions where both are set for real, and an inherited push payload would otherwise leak into every case.Judgment calls
\n, each line anchored) rather than with a multiline flag, so[[:space:]]*cannot swallow a newline and match a value on the next line — the strictly narrower reading of the specified pattern.mainsuppresses every fleet, not just the one whose surface the docs edit touched. Scoping it would mean the trailer naming a surface, a larger interface than this buys — and in the observed case (a comment reword that fanned four fleets) all four suppressions would have been correct.Provenance
shellcheck -xon all.github/bump-callersscripts + test suites: clean;bash .github/bump-callers/tests/test_preflight.sh: 389 passed, 0 failed;test_bump_callers.sh: 437 passed, 0 failed;test_paths_contract.sh: 70 passed, 0 failed;python3 .github/agents-md-integrity/check_agents_md.py --root .: passed (2 warnings: the 155>150 aspirational length, and the pre-existing missing-CODEOWNERS warning);gh api repos/Comfy-Org/github-workflowsconfirmssquash_merge_commit_message: COMMIT_MESSAGES; the squashed-message format (*on first lines only, trailers at column 0, last commit's message last) checked against this repo's own merged history; the 2048-commit cap and the absence of a.sizefield checked against GitHub's push webhook payload documentation;git logconfirmseaee6dfis a comment-reword docs commit.c4ef875(paragraph-boundary trailer blocks; the hand-off walk restricted to the watched surface; the guard bound matched to the gate's and made real viagit log -n; a distinct log line for an unevaluable range; the contradictory re-point echo suppressed on the hand-off path) — each with a test that fails on the pre-fix script. One earlier finding remains deferred rather than fixed — a bump run is also the catch-up for an earlier run that failed at the token mint, was cancelled, or never started (the 300-filepaths:evaluation limit), and declining here declines that catch-up. Nothing in the push payload can observe it and the current pin is unreadable from the preflight, so closing it needs a new source of state. It is documented as a KNOWN LIMIT in both the script header and the README, and filed as a follow-up. Every other panel finding is fixed in this branch.Residual
main, which only exists once this merges. The functional suite drives the real script with real payload shapes and real git history instead; the first trailered docs commit after merge is the live confirmation, andworkflow_dispatchis the recovery if it misbehaves.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.