Run CI when a stacked PR is retargeted onto main - #87
Merged
Conversation
#86 sat on main-facing review for a while with zero checks on it, and the reason was not a failure - it was that nothing had ever run. It was based on feat/coverage-guards, so the `branches: [main]` filter (which matches the PR's base, not its head) kept both workflows off it entirely. Retargeting with `gh pr edit 86 --base main` should have fixed that and did not: a `pull_request` trigger with no `types:` defaults to [opened, synchronize, reopened], and retargeting fires `edited`. `gh pr close 86 && gh pr reopen 86` was what finally got a run, because `reopened` is in the default set. The failure mode is what makes this worth fixing rather than remembering. A skipped check is loud; an empty checks list is not. The PR page showed no checks at all, which reads as "nothing to report" rather than "nothing looked". So `edited` joins the types on CI and Integration Tests. Release is untouched - it triggers on `v*` tag pushes only and has no pull_request trigger to get wrong. `edited` also fires on every title and description edit, and a prose fix costing a ~7 minute integration run against real staging is a bad trade, so each job carries `github.event.action != 'edited' || github.event.changes.base != null`. `changes.base` is in the payload only when the base moved - the documented shape is base.ref.from and base.sha.from, verified against octokit's pull_request$edited schema rather than assumed - so a retitle sends changes.title, the guard skips, and a skipped job costs no minutes. On push there is no `action` at all, so the guard is a no-op there. `branches: [main]` stays. Dropping it would give stacked PRs CI while they still point at their parent, but it would also run the full matrix twice for every one of them - once against the parent and again after the retarget - and the second run is the one that matters, since it is the only one that tests the merge against what will actually be merged into. With `edited` wired up that run now happens on its own. The gap that remains is real: a stacked PR gets no signal until it is retargeted, and lefthook plus `workflow_dispatch` on the integration workflow are the cover for that in the meantime. Deliberately not done: `workflow_dispatch` on CI. It looks like the obvious mitigation for the paragraph above and is not one - a dispatched run builds the branch head rather than refs/pull/N/merge, and its result does not attach to the PR, so it would answer a different question than the one being asked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
| Filename | Overview |
|---|---|
| .github/workflows/ci.yml | Adds retarget-aware triggering and base-change guards to both CI jobs; the event behavior is coherent, but no automated regression coverage was added. |
| .github/workflows/integration-tests.yml | Applies the same guarded edited-event handling to integration tests while retaining push and manual triggers, without regression coverage. |
Prompt To Fix All With AI
### Issue 1
.github/workflows/ci.yml:12
**Retargeting guard lacks regression coverage**
The new `edited` trigger and `changes.base` guard have no automated coverage for base retargets versus title or body edits, so a future workflow change can silently restore the missing-check behavior or run expensive jobs for prose-only edits.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "fix: run CI when a stacked PR is retarge..." | Re-trigger Greptile
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.
The gap
#86 showed no checks at all for most of its life. Not a red X — an empty checks list, which reads as "nothing to report" rather than "nothing looked". Two separate causes, in sequence:
feat/coverage-guards. Thebranches: [main]filter onpull_requestmatches the PR's base, not its head, so neither workflow ever fired.gh pr edit 86 --base mainshould have fixed that and didn't. Apull_requesttrigger with notypes:defaults to[opened, synchronize, reopened], and retargeting firesedited.gh pr close 86 && gh pr reopen 86is what finally produced a run, becausereopenedis in the default set.What changed
editedis added to thetypes:on CI and Integration Tests. Every job in both gets:Release is untouched — it triggers on
v*tag pushes only and has nopull_requesttrigger to get wrong.The trade-off on
editededitedalso fires on every title and description edit. Adding it bare would mean a typo fix in a PR description costs a full lint + build + codegen check +skill adde2e, plus a ~7 minute integration run against real staging withNOTTE_API_KEY. That is a bad trade often enough to be worth three lines of guard.changes.baseis present in the payload only when the base actually moved. Its documented shape is:Verified against octokit's
pull_request$editedschema rather than assumed —changeshasbody,titleandbase, none of them required. A retitle sendschanges.title, the guard evaluates false, and the job skips. A skipped job costs no runner minutes, and skipped counts as success for required checks.On
pushthere is noactionat all, so the first clause is true and the guard is a no-op.The cost of the gate is that it has to be repeated per job (three places, since there's no workflow-level
if). The alternative — baretypes:with no gate — is one line shorter and burns ~10 minutes of CI on every prose edit. The gate wins.On keeping
branches: [main]Recommendation: keep it. Dropping it would give stacked PRs CI while they still point at their parent, but it would also run the full matrix twice for every stacked PR — once against the parent and again after the retarget — and the second run is the one that matters, because it's the only one testing the merge against what actually gets merged into. With
editedwired up, that run now happens on its own instead of needing a close/reopen.The residual gap is real and worth naming: a stacked PR gets no CI signal until it is retargeted. Cover for that today is lefthook's pre-commit hooks (which already run
check-endpointsandcheck-skills) andworkflow_dispatchon the integration workflow.Deliberately not done
workflow_dispatchon CI. It looks like the obvious mitigation for the paragraph above and isn't one: a dispatched run builds the branch head rather thanrefs/pull/N/merge, and its result doesn't attach to the PR. It would answer a different question than the one being asked.ready_for_review. Also outside the default set, but not a gap here —openedfires for draft PRs too and there's no draft filter, so drafts already get CI.Verification
actionlint(v1.7.12, already installed) is clean on all three workflows, before and after.Not verifiable without merging: that the
edited+changes.basepath actually fires end-to-end. The trigger config onmainis what GitHub reads, so this PR's own checks can't exercise it. The first stacked PR retargeted after this lands is the test.🤖 Generated with Claude Code