Skip to content

Commit 9414cce

Browse files
devantlerCopilot
andauthored
fix(ci): cancel stale merge queue runs for the same PR (#4013)
* fix(ci): cancel stale merge queue runs for the same PR Add a cancel-stale-merge-queue job that runs only for merge_group events. It strips the SHA suffix from the merge queue ref to find a stable per-PR prefix, then cancels any older in-progress or queued workflow runs that share the same prefix. This fixes the issue where re-entering the merge queue or a push to main left stale CI runs running because the workflow-level concurrency group included the unique SHA from the merge queue ref. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(ci): address review feedback on merge queue cancellation - Paginate listWorkflowRuns with per_page: 100 to avoid missing stale runs when there are many in-progress/queued runs - Add event: 'merge_group' filter to scope listings to merge queue runs - Wrap cancelWorkflowRun in try/catch to handle race conditions where a run completes between listing and cancelling (409/422) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2dce5f9 commit 9414cce

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,77 @@ concurrency:
3131
permissions: {}
3232

3333
jobs:
34+
# Cancel stale merge queue runs for the same PR.
35+
# Merge queue refs include a unique SHA suffix (gh-readonly-queue/<base>/pr-<id>-<sha>),
36+
# so the workflow-level concurrency group can't deduplicate them. This job strips the
37+
# SHA to find older runs for the same queue entry and cancels them.
38+
cancel-stale-merge-queue:
39+
name: 🧹 Cancel Stale Merge Queue Runs
40+
if: github.event_name == 'merge_group'
41+
runs-on: ubuntu-latest
42+
timeout-minutes: 2
43+
permissions:
44+
actions: write
45+
steps:
46+
- name: Harden the runner (Audit all outbound calls)
47+
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
48+
with:
49+
egress-policy: audit
50+
51+
- name: Cancel previous runs for same merge queue entry
52+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
53+
with:
54+
script: |
55+
const ref = context.ref;
56+
const match = ref.match(/^(refs\/heads\/gh-readonly-queue\/.+)-[0-9a-f]+$/);
57+
if (!match) {
58+
console.log('Not a merge queue ref, skipping');
59+
return;
60+
}
61+
const stablePrefix = match[1];
62+
63+
for (const status of ['in_progress', 'queued']) {
64+
let page = 1;
65+
while (true) {
66+
const { data: { workflow_runs: runs } } = await github.rest.actions.listWorkflowRuns({
67+
owner: context.repo.owner,
68+
repo: context.repo.repo,
69+
workflow_id: 'ci.yaml',
70+
event: 'merge_group',
71+
status,
72+
per_page: 100,
73+
page,
74+
});
75+
76+
if (runs.length === 0) break;
77+
78+
for (const run of runs) {
79+
if (run.run_number >= context.runNumber) continue;
80+
const runRef = `refs/heads/${run.head_branch}`;
81+
const runMatch = runRef.match(/^(refs\/heads\/gh-readonly-queue\/.+)-[0-9a-f]+$/);
82+
if (runMatch && runMatch[1] === stablePrefix) {
83+
console.log(`Cancelling stale run ${run.id} (${runRef})`);
84+
try {
85+
await github.rest.actions.cancelWorkflowRun({
86+
owner: context.repo.owner,
87+
repo: context.repo.repo,
88+
run_id: run.id,
89+
});
90+
} catch (error) {
91+
if (error && (error.status === 409 || error.status === 422)) {
92+
console.log(`Skipping run ${run.id}; it is no longer cancellable (${error.status})`);
93+
continue;
94+
}
95+
throw error;
96+
}
97+
}
98+
}
99+
100+
if (runs.length < 100) break;
101+
page += 1;
102+
}
103+
}
104+
34105
# Wait for sufficient GitHub API rate limit before proceeding
35106
rate-limit-gate:
36107
name: ⏳ Rate Limit Gate

0 commit comments

Comments
 (0)