Skip to content

Commit 0cdbc58

Browse files
chore: add workflow to close stale PRs with failed workflows (#6927)
1 parent 2da0465 commit 0cdbc58

File tree

1 file changed

+80
-0
lines changed

1 file changed

+80
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Close stale PRs with failed workflows
2+
3+
on:
4+
schedule:
5+
- cron: '0 3 * * *' # runs daily at 03:00 UTC
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
issues: write
11+
pull-requests: write
12+
13+
jobs:
14+
close-stale:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Close stale PRs
18+
uses: actions/github-script@v7
19+
with:
20+
github-token: ${{ secrets.GITHUB_TOKEN }}
21+
script: |
22+
const mainBranches = ['main', 'master'];
23+
const cutoffDays = 14;
24+
const cutoff = new Date();
25+
cutoff.setDate(cutoff.getDate() - cutoffDays);
26+
27+
const { data: prs } = await github.rest.pulls.list({
28+
owner: context.repo.owner,
29+
repo: context.repo.repo,
30+
state: 'open',
31+
per_page: 100
32+
});
33+
34+
for (const pr of prs) {
35+
const updated = new Date(pr.updated_at);
36+
if (updated > cutoff) continue;
37+
38+
const commits = await github.paginate(github.rest.pulls.listCommits, {
39+
owner: context.repo.owner,
40+
repo: context.repo.repo,
41+
pull_number: pr.number,
42+
per_page: 100
43+
});
44+
45+
const meaningfulCommits = commits.filter(c => {
46+
const msg = c.commit.message.toLowerCase();
47+
const isMergeFromMain = mainBranches.some(branch =>
48+
msg.startsWith(`merge branch '${branch}'`) ||
49+
msg.includes(`merge remote-tracking branch '${branch}'`)
50+
);
51+
return !isMergeFromMain;
52+
});
53+
54+
const { data: checks } = await github.rest.checks.listForRef({
55+
owner: context.repo.owner,
56+
repo: context.repo.repo,
57+
ref: pr.head.sha
58+
});
59+
60+
const hasFailed = checks.check_runs.some(c => c.conclusion === 'failure');
61+
const allCompleted = checks.check_runs.every(c => c.status === 'completed');
62+
63+
if (meaningfulCommits.length === 0 && hasFailed && allCompleted) {
64+
console.log(`Closing PR #${pr.number} (${pr.title})`);
65+
66+
await github.rest.issues.createComment({
67+
owner: context.repo.owner,
68+
repo: context.repo.repo,
69+
issue_number: pr.number,
70+
body: `This pull request has been automatically closed because its workflows failed and it has been inactive for more than ${cutoffDays} days. Please fix the workflows and reopen if you'd like to continue. Merging from main/master alone does not count as activity.`
71+
});
72+
73+
await github.rest.pulls.update({
74+
owner: context.repo.owner,
75+
repo: context.repo.repo,
76+
pull_number: pr.number,
77+
state: 'closed'
78+
});
79+
}
80+
}

0 commit comments

Comments
 (0)