Skip to content

ci: delete superseded pending-approval runs on fork PRs - #9531

Open
bj97301 wants to merge 1 commit into
pingdotgg:mainfrom
bj97301:fix/delete-superseded-pending-runs
Open

ci: delete superseded pending-approval runs on fork PRs#9531
bj97301 wants to merge 1 commit into
pingdotgg:mainfrom
bj97301:fix/delete-superseded-pending-runs

Conversation

@bj97301

@bj97301 bj97301 commented Sep 4, 2026

Copy link
Copy Markdown

What Changed

Adds .github/workflows/pr-superseded-runs.yml, a pull_request_target workflow that runs on synchronize for fork PRs only. It lists the repo's pull_request runs for the PR's head branch that are parked in action_required, keeps the ones for the current head commit, and deletes the rest. It never checks out PR code and needs only actions: write.

Why

Fixes #9529.

Fork PRs need maintainer approval before pull_request workflows may run. Until then every push leaves one jobless run per workflow parked in "action required". The existing concurrency groups never reach them because a run that never started cannot be cancelled by a newer one, so they pile up (PR #7434 accumulated 68 across 17 commits). When the PR closes, GitHub flips every parked run to failure within seconds and emails the author once per run with "No jobs were run".

Cleaning up on closed cannot win that race, so this cleans up on each push instead. After a push, only the current head's runs stay pending, so a close produces one email per workflow instead of one per workflow per commit. The current head's runs still flip on close; that part is GitHub's behavior and not addressable from the repo.

Delete rather than cancel: the API reports these runs as status: completed, conclusion: action_required, so the cancel endpoint would reject them. Delete is the endpoint GitHub itself uses when it purges runs awaiting approval after 30 days, and nothing is lost because these runs never executed a job.

Verified against open PR #6191 by dry-running the same list-and-filter logic with gh api: 17 runs for the four replaced commits selected, 5 runs for the current head kept. The delete call itself needs repository write access, so it could only be exercised once merged.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (n/a)
  • I included a video for animation/interaction changes (n/a)

Written with Claude Fable 5.1 in Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
CI-only automation using pull_request_target with no PR checkout; writes only via GitHub Actions API to delete superseded pending runs on fork PRs.

Overview
Adds a pull_request_target workflow that runs on every fork PR synchronize event to trim stale workflow runs stuck in action_required (waiting for maintainer approval).

On each push it lists pull_request runs for the PR head branch in action_required, keeps runs for the current head SHA, and deletes runs tied to older commits on the same fork head repo. It is fork-only, uses actions/github-script (no checkout of PR code), and scopes job permissions to actions: write with per-PR concurrency so cleanup does not pile up.

This reduces how many parked runs flip to failure and email the author when the PR closes—addressing the pile-up that concurrency cannot cancel because those runs never start.

Reviewed by Cursor Bugbot for commit 9335fb4. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add CI workflow to delete superseded pending runs on fork PRs

  • Adds pr-superseded-runs.yml triggered by pull_request_target synchronization events to remove stale action_required runs on fork pull requests.
  • Uses actions/github-script to find runs matching the source branch, pull_request event, and action_required status. It deletes runs from the same fork repository with an older head SHA.
  • Execution is serialized per pull request using concurrency, canceling older cleanup jobs.
  • Behavioral Change: Grants actions: write and contents: read permissions to the workflow to delete runs via the Actions API.
📊 Macroscope summarized 9335fb4. 1 file reviewed, 2 issues evaluated, 0 issues filtered, 2 comments posted

🗂️ Filtered Issues

Fork PRs park one jobless "action required" run per workflow per push until
a maintainer approves. Concurrency never cancels them because they never
start, and when the PR closes GitHub flips every parked run to failure and
emails the author once per run (68 emails for pingdotgg#7434).

Add a pull_request_target workflow that, on each push to a fork PR, deletes
the parked runs whose commit a newer push has replaced. Only the current
head keeps its pending runs.

Fixes pingdotgg#9529

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T00:54:12.271518Z 9335fb4 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 4, 2026
});

const superseded = runs.filter(
(run) => run.head_repository?.id === pr.head.repo.id && run.head_sha !== pr.head.sha,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High workflows/pr-superseded-runs.yml:47

An older synchronize run can delete the current commit's action_required runs, leaving the PR's actual head without a run to execute. The filter compares against the stale event payload's pr.head.sha, so after commit B replaces A, A's cleanup selects B as superseded; cancel-in-progress is asynchronous and does not prevent this race. Re-fetch the PR head and revalidate it immediately before each deletion, skipping cleanup when the head has changed.

🤖 Copy this AI Prompt to have your agent fix this:
In file @.github/workflows/pr-superseded-runs.yml around line 47:

An older `synchronize` run can delete the current commit's `action_required` runs, leaving the PR's actual head without a run to execute. The filter compares against the stale event payload's `pr.head.sha`, so after commit B replaces A, A's cleanup selects B as superseded; `cancel-in-progress` is asynchronous and does not prevent this race. Re-fetch the PR head and revalidate it immediately before each deletion, skipping cleanup when the head has changed.

script: |
const pr = context.payload.pull_request;

const runs = await github.paginate(github.rest.actions.listWorkflowRunsForRepo, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium workflows/pr-superseded-runs.yml:37

This cleanup leaves superseded runs undeleted once the PR has more than 1,000 matching action_required runs, so closing the PR still generates failure notifications for those retained runs. github.paginate follows pages, but listWorkflowRunsForRepo caps filtered results at 1,000; partition the queries by created ranges (or use another bounded enumeration) so every parked run is considered.

🤖 Copy this AI Prompt to have your agent fix this:
In file @.github/workflows/pr-superseded-runs.yml around line 37:

This cleanup leaves superseded runs undeleted once the PR has more than 1,000 matching `action_required` runs, so closing the PR still generates failure notifications for those retained runs. `github.paginate` follows pages, but `listWorkflowRunsForRepo` caps filtered results at 1,000; partition the queries by `created` ranges (or use another bounded enumeration) so every parked run is considered.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9335fb4. Configure here.


const superseded = runs.filter(
(run) => run.head_repository?.id === pr.head.repo.id && run.head_sha !== pr.head.sha,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale SHA deletes current pending runs

Low Severity

The keep-filter uses the synchronize event’s frozen head.sha, so a re-run of an older job—or a job that listed after a later push—treats the newer commit’s action_required runs as superseded and deletes them. The PR is then left with no pending approval runs for the actual head.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9335fb4. Configure here.

@macroscopeapp

macroscopeapp Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This adds a privileged pull_request_target workflow that automatically deletes pending CI runs for fork PRs, changing GitHub Actions behavior and run retention. Unresolved findings identify a race that can delete current-head runs and an enumeration limit that can leave stale runs behind.

Not approved because:

  • 2 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Closing an external PR turns every jobless workflow run into a failure and sends an email flood

1 participant