1515 runs-on : ubuntu-latest
1616 steps :
1717 - name : Close stale PRs
18- uses : actions/github-script@v7
18+ uses : actions/github-script@v8
1919 with :
2020 github-token : ${{ secrets.GITHUB_TOKEN }}
2121 script : |
@@ -24,57 +24,133 @@ jobs:
2424 const cutoff = new Date();
2525 cutoff.setDate(cutoff.getDate() - cutoffDays);
2626
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- });
27+ console.log(`Checking PRs older than: ${cutoff.toISOString()}`);
3328
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, {
29+ try {
30+ const { data: prs } = await github.rest.pulls.list({
3931 owner: context.repo.owner,
4032 repo: context.repo.repo,
41- pull_number: pr.number,
33+ state: 'open',
34+ sort: 'updated',
35+ direction: 'asc',
4236 per_page: 100
4337 });
4438
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- });
39+ console.log(`Found ${prs.length} open PRs to check`);
5340
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- });
41+ for (const pr of prs) {
42+ try {
43+ const updated = new Date(pr.updated_at);
44+
45+ if (updated > cutoff) {
46+ console.log(`⏩ Skipping PR #${pr.number} - updated recently`);
47+ continue;
48+ }
49+
50+ console.log(`🔍 Checking PR #${pr.number}: "${pr.title}"`);
51+
52+ // Get commits
53+ const commits = await github.paginate(github.rest.pulls.listCommits, {
54+ owner: context.repo.owner,
55+ repo: context.repo.repo,
56+ pull_number: pr.number,
57+ per_page: 100
58+ });
59+
60+ const meaningfulCommits = commits.filter(c => {
61+ const msg = c.commit.message.toLowerCase();
62+ const date = new Date(c.commit.committer.date);
63+ const isMergeFromMain = mainBranches.some(branch =>
64+ msg.startsWith(`merge branch '${branch}'`) ||
65+ msg.includes(`merge remote-tracking branch '${branch}'`)
66+ );
67+
68+ return !isMergeFromMain && date > cutoff;
69+ });
70+
71+ // Get checks with error handling
72+ let hasFailedChecks = false;
73+ let allChecksCompleted = false;
74+ let hasChecks = false;
75+
76+ try {
77+ const { data: checks } = await github.rest.checks.listForRef({
78+ owner: context.repo.owner,
79+ repo: context.repo.repo,
80+ ref: pr.head.sha
81+ });
82+
83+ hasChecks = checks.check_runs.length > 0;
84+ hasFailedChecks = checks.check_runs.some(c => c.conclusion === 'failure');
85+ allChecksCompleted = checks.check_runs.every(c =>
86+ c.status === 'completed' || c.status === 'skipped'
87+ );
88+ } catch (error) {
89+ console.log(`⚠️ Could not fetch checks for PR #${pr.number}: ${error.message}`);
90+ }
91+
92+ // Get workflow runs with error handling
93+ let hasFailedWorkflows = false;
94+ let allWorkflowsCompleted = false;
95+ let hasWorkflows = false;
96+
97+ try {
98+ const { data: runs } = await github.rest.actions.listWorkflowRuns({
99+ owner: context.repo.owner,
100+ repo: context.repo.repo,
101+ head_sha: pr.head.sha,
102+ per_page: 50
103+ });
104+
105+ hasWorkflows = runs.workflow_runs.length > 0;
106+ hasFailedWorkflows = runs.workflow_runs.some(r => r.conclusion === 'failure');
107+ allWorkflowsCompleted = runs.workflow_runs.every(r =>
108+ ['completed', 'skipped', 'cancelled'].includes(r.status)
109+ );
110+
111+ console.log(`PR #${pr.number}: ${runs.workflow_runs.length} workflow runs found`);
112+
113+ } catch (error) {
114+ console.log(`⚠️ Could not fetch workflow runs for PR #${pr.number}: ${error.message}`);
115+ }
116+
117+ console.log(`PR #${pr.number}: ${meaningfulCommits.length} meaningful commits`);
118+ console.log(`Checks - has: ${hasChecks}, failed: ${hasFailedChecks}, completed: ${allChecksCompleted}`);
119+ console.log(`Workflows - has: ${hasWorkflows}, failed: ${hasFailedWorkflows}, completed: ${allWorkflowsCompleted}`);
120+
121+ // Combine conditions - only consider if we actually have checks/workflows
122+ const hasAnyFailure = (hasChecks && hasFailedChecks) || (hasWorkflows && hasFailedWorkflows);
123+ const allCompleted = (!hasChecks || allChecksCompleted) && (!hasWorkflows || allWorkflowsCompleted);
124+
125+ if (meaningfulCommits.length === 0 && hasAnyFailure && allCompleted) {
126+ console.log(`✅ Closing PR #${pr.number} (${pr.title})`);
127+
128+ await github.rest.issues.createComment({
129+ owner: context.repo.owner,
130+ repo: context.repo.repo,
131+ issue_number: pr.number,
132+ body: `This pull request has been automatically closed because its workflows or checks 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.`
133+ });
134+
135+ await github.rest.pulls.update({
136+ owner: context.repo.owner,
137+ repo: context.repo.repo,
138+ pull_number: pr.number,
139+ state: 'closed'
140+ });
141+
142+ console.log(`✅ Successfully closed PR #${pr.number}`);
143+ } else {
144+ console.log(`⏩ Not closing PR #${pr.number} - conditions not met`);
145+ }
59146
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- });
147+ } catch (prError) {
148+ console.error(`❌ Error processing PR #${pr.number}: ${prError.message}`);
149+ continue;
150+ }
79151 }
80- }
152+
153+ } catch (error) {
154+ console.error(`❌ Fatal error: ${error.message}`);
155+ throw error;
156+ }
0 commit comments