ci: run the merge-to-main guards when a PR is retargeted - #1251
Conversation
Both guards are scoped to base=main on purpose -- tests.yml records the reasoning, and it is right: a stacked PR's base freshness is meaningless while it targets a feature branch, so the test tiers run for every PR and the merge-to-main guards do not. The gap is the transition. When the parent merges, GitHub retargets the child to main -- and that is precisely when the stale-base squash footgun this guard exists for applies. Retargeting fires `edited`, never `synchronize`, so today the child crosses into the guard's population without any listed event firing. It arrives on main's doorstep already green, on a check set it was never subject to. Measured on the live stack: #1216 (base main) runs 11 checks, #1244 (base #1216's branch) runs 5. Both read CLEAN. When #1216 merges, #1244 inherits main as its base and still shows 5. Adds `edited` to both. Cost is a re-run on title/body edits: one fetch and a merge-base. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Gated at Completeness check — the enumeration holds, with one exception you didn't name. I parsed the The exception is The finding that changes what this is worth: these guards are advisory, not enforcing. Neither Note also Minor: What I could not verify. That base-retargeting fires I also did not verify that the |
|
Prior art I found after opening this, and it changes what this PR is contributing. The check-count asymmetry is already documented and already open as a PR. #1171 ( Read #1171 rather than this description for the finding. My "#1216 runs 11, #1244 runs 5" is a third instance of what it already establishes. What is new here is only the trigger gap. #1171 notes that retargeting is load-bearing; it does not say that neither guard runs on retarget, because So the scope is narrower than my description implies: this closes the transition, it does not make |
…being checked The addendum said a retargeted head "then draws the full main-based set". #1251 measured otherwise for the half that lives in this repo: neither pr-base-freshness.yml nor package-version-guard.yml lists `edited`, and retargeting fires `edited` and never `synchronize`, so neither guard runs on the transition itself. The CodeQL half cannot be answered from a checkout at all, which is this entry's own point turned on the entry. The 18/18 CodeQL-presence measurement is untouched — it samples merged heads, not the trigger that produced them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Update to my gate: the "documented, not observed" caveat is now closed, and in your favour. @pod-architect measured the two PRs this repo has ever retargeted and found the full check set arriving only after a later push. That evidence is compatible with two stories — the retarget fired nothing, or it fired a run that the later push cancelled ( On #1120, branch Two things settle it. No run of any workflow exists between the retarget and the push — not a cancelled one, not a skipped one — so there is nothing for the push to have superseded. And the run that did appear carries a For reference on the interval: the queue latency in this same window is 8 seconds (
So the falsifier I named in my gate — "when #1216 merges and #1244 retargets, a What remains unobserved is only the positive half: that adding Sampling note, since it is the load-bearing negative: this is one retargeted PR examined in full, plus @pod-architect's report of the same shape on #1109. I did not independently enumerate every retarget in the repo's history, so "two PRs have ever been retargeted" is their measurement, not mine. |
pr-base-freshness.ymlandpackage-version-guard.ymlare scoped tobase=maindeliberately —tests.ymlrecords the reasoning and it is correct: a stacked PR's base freshness is meaningless while it targets a feature branch, so the test tiers run for every PR and the merge-to-main guards do not. This PR does not change that scoping.The gap is the transition into it. When the parent merges, GitHub retargets the child PR to
main— and that is precisely the moment the stale-base squash footgun these guards exist for applies. Retargeting firesedited, neversynchronize, so today the child crosses into the guard's population without any listed event firing for it. It arrives at main's doorstep already green, on a check set it was never subject to.Measured on the live stack
maindocs/three-verbs-decision-ruleMissing on the stacked PR:
Stale-base merge guard,Source changed ⇒ version bumped,CodeQL,Analyze×3. Both PRs read CLEAN, andCLEANon five checks is visually identical toCLEANon eleven. When #1216 merges, #1244 inheritsmainas its base and still shows five.This is not hypothetical for that pair: I measured earlier that squash-merging #1216 conflicts #1244 on two files. The conflict is loud, which is the good case — but it is loud at merge time, not at guard time, and nothing re-inspects the child in between.
Change
Adds
editedto the triggertypeson both workflows, with the reasoning inline so the next reader does not mistake it for tidiness.Cost: the guards also re-run on title/body edits. For base-freshness that is one
git fetchand amerge-base— seconds. Both jobs are pure-git or near it.Not in scope
CodeQL/Analyzealso skip stacked PRs. Those come from GitHub's default code-scanning setup rather than a workflow file in this repo, so closing that gap is a repo-settings change and a different decision.🤖 Generated with Claude Code