Skip to content

ci: Add run cancellation for branch whose PR is closed - #5827

Open
backspace wants to merge 2 commits into
mainfrom
ci-cancel-runs-on-pr-close
Open

ci: Add run cancellation for branch whose PR is closed#5827
backspace wants to merge 2 commits into
mainfrom
ci-cancel-runs-on-pr-close

Conversation

@backspace

Copy link
Copy Markdown
Contributor

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.

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>
@backspace
backspace marked this pull request as ready for review August 20, 2026 00:27

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +40 to +41
| select(.status == \"queued\" or .status == \"in_progress\")
| select(.id != ${SELF})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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>
@backspace
backspace requested a review from a team August 20, 2026 00:51
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.

2 participants