Skip to content

Run CI when a stacked PR is retargeted onto main - #87

Merged
giordano-lucas merged 1 commit into
mainfrom
fix/ci-stacked-pr-triggers
Aug 31, 2026
Merged

Run CI when a stacked PR is retargeted onto main#87
giordano-lucas merged 1 commit into
mainfrom
fix/ci-stacked-pr-triggers

Conversation

@giordano-lucas

Copy link
Copy Markdown
Member

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:

  1. It was based on feat/coverage-guards. The branches: [main] filter on pull_request matches the PR's base, not its head, so neither workflow ever fired.
  2. Retargeting with gh pr edit 86 --base main should have fixed that and didn't. A pull_request trigger with no types: defaults to [opened, synchronize, reopened], and retargeting fires edited. gh pr close 86 && gh pr reopen 86 is what finally produced a run, because reopened is in the default set.

What changed

edited is added to the types: on CI and Integration Tests. Every job in both gets:

if: github.event.action != 'edited' || github.event.changes.base != null

Release is untouched — it triggers on v* tag pushes only and has no pull_request trigger to get wrong.

The trade-off on edited

edited also 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 add e2e, plus a ~7 minute integration run against real staging with NOTTE_API_KEY. That is a bad trade often enough to be worth three lines of guard.

changes.base is present in the payload only when the base actually moved. Its documented shape is:

"changes": { "base": { "ref": { "from": "..." }, "sha": { "from": "..." } } }

Verified against octokit's pull_request$edited schema rather than assumed — changes has body, title and base, none of them required. A retitle sends changes.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 push there is no action at 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 — bare types: 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 edited wired 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-endpoints and check-skills) and workflow_dispatch on the integration workflow.

Deliberately not done

  • workflow_dispatch on CI. It looks like the obvious mitigation for the paragraph above and isn't one: a dispatched run builds the branch head rather than refs/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 — opened fires 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.base path actually fires end-to-end. The trigger config on main is 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

#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>
@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown

Greptile Summary

This PR makes CI and integration workflows react when a pull request is retargeted onto main, while job-level guards prevent title and description edits from consuming runners.

  • Adds edited to both workflows’ pull_request event types.
  • Runs jobs for edited events only when the pull request base changed.
  • Preserves existing push and manual integration-workflow behavior.

Confidence Score: 4/5

The PR appears safe to merge, with the non-blocking concern that its new workflow-event behavior lacks automated regression coverage.

The edited-event conditions preserve existing event paths and correctly limit prose-only edits, but no test protects the retargeting behavior from future workflow regressions.

Files Needing Attention: .github/workflows/ci.yml and .github/workflows/integration-tests.yml

Important Files Changed

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.

Fix all with Greploop Fix All in Claude Code

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

@giordano-lucas
giordano-lucas merged commit 7d3ffac into main Aug 31, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant