ci: Add run cancellation for branch whose PR is closed - #5827
Conversation
Once a pull request is merged or closed its running jobs can no longer tell anyone anything, but they hold their runners until they finish. That is ordinarily just waste; with the queue hundreds of jobs deep it delays every other pull request, because those runners are precisely what the queue is waiting for. `concurrency` does not cover this. It supersedes a run only when a newer run starts in the same group, and closing a pull request starts nothing. Fork pull requests are skipped, since their token is read-only and the cancel calls would fail. This run excludes itself from the runs it cancels, being one of them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ccff845716
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| | select(.status == \"queued\" or .status == \"in_progress\") | ||
| | select(.id != ${SELF}) |
There was a problem hiding this comment.
Exempt close-triggered cleanup runs
When an internal PR touching packages/observability/** closes, .github/workflows/observability-preview.yml simultaneously starts its preview-cleanup job (types: [closed], with the cleanup guarded by github.event.action == 'closed'). This selection cancels every queued or running workflow on the branch except itself, so it will usually cancel that deliberately close-triggered cleanup before it removes the staging Grafana preview, leaving resources and the sticky comment stale until the fallback sweep runs. Exclude workflows started for the current close event, or explicitly preserve this cleanup workflow.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
[Claude Code 🤖] Confirmed and fixed in c295c2f — thank you, this was a real bug.
I verified the mechanism before changing anything: observability-preview.yml triggers on types: [opened, synchronize, reopened, closed], and its preview-cleanup job is guarded by github.event.action == 'closed'. So closing a PR that touches packages/observability/** starts the teardown at the same moment this workflow starts cancelling — and the teardown would have been cancelled before it removed the Grafana preview.
Rather than allow-listing that workflow by name, the fix filters on time: a run started by the close event is created at or after the pull request's closed_at, so comparing created_at < closed_at exempts it. That covers this run itself, preview-cleanup, and any close-triggered teardown added later without anyone needing to remember to add it to a list. If closed_at cannot be read the step now cancels nothing rather than guessing.
| run: | | ||
| # Excludes this run: it is itself a run on the branch being cleaned up, | ||
| # and cancelling it would stop the loop partway through. | ||
| ids=$(gh api "repos/${REPO}/actions/runs?branch=${BRANCH}&per_page=100" \ |
There was a problem hiding this comment.
Paginate the workflow-run listing
When a busy branch has more than 100 workflow runs, this request examines only the first page, so queued or in-progress runs on later pages survive the PR close and continue consuming capacity. The gh api manual states that only --paginate makes additional requests for all result pages; add it here so the workflow fulfills its stated all-runs cleanup behavior.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
[Claude Code 🤖] Correct, fixed in c295c2f.
The listing now passes --paginate. I also added a server-side status filter and query queued and in_progress separately, because paginating the unfiltered endpoint on a long-lived branch would walk its entire run history — potentially many pages of completed runs — to find the handful that can still be cancelled. Filtering server-side keeps the pagination bounded to runs that are actually candidates.
One incidental find while testing it: I had named the loop variable status, which is read-only in zsh. The workflow runs under bash so it would have worked, but it broke when I exercised the query locally, so it is now run_status.
Closing a pull request can start work as well as end it. observability-preview tears down the PR's Grafana preview from a `closed` trigger, so cancelling every run on the branch would cancel that teardown too, stranding the preview and its sticky comment until the fallback sweep notices. Those runs are exactly the ones created at or after the close, so comparing against the pull request's closed_at separates them without naming individual workflows — a later teardown added elsewhere is covered by the same rule. When closed_at cannot be read, nothing is cancelled at all. The listing is also paginated now, and filtered by status server-side so paginating cannot walk a long-lived branch's entire run history to find the few runs that can still be cancelled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
During this ongoing Actions backlog, I’ve noticed that jobs still run even when their PR has closed. This adds a workflow to terminate them.