Closed blocked PRs more often

This commit is contained in:
bbhtt
2026-02-26 06:57:11 +05:30
parent fb395d7dc4
commit 3347d58454
+144 -21
View File
@@ -1,30 +1,153 @@
name: "Close stale blocked PRs" name: "Close stale blocked PRs"
on: on:
schedule: schedule:
- cron: "0 0 * * 6" - cron: "0 0 * * 1"
workflow_dispatch: workflow_dispatch:
inputs:
dry_run:
description: "Dry run"
type: boolean
default: false
jobs: jobs:
stale: close-blocked:
permissions: permissions:
pull-requests: write pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# 9.1.0 - name: Close stale blocked PRs
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # 8.0.0
with: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
repo-token: ${{ secrets.GITHUB_TOKEN }} env:
stale-pr-message: |- DRY_RUN: ${{ inputs.dry_run || 'false' }}
This pull request has not received any updates and it will be DAYS_THRESHOLD: "14"
automatically closed in 7 days. Please comment or reopen if LABEL_BLOCKED: "blocked"
necessary. LABEL_CHECKS: "pr-check-blocked"
days-before-stale: -1 COMMENT_BLOCKED: |-
days-before-close: -1 This pull request has been marked as blocked for {days} days
days-before-pr-stale: 23 and is not ready for inclusion. It is being automatically closed.
days-before-pr-close: 7 COMMENT_CHECKS: |-
any-of-pr-labels: blocked,pr-check-blocked This pull request is failing checks for {days} days
remove-stale-when-updated: false and is not ready for inclusion. It is being automatically closed.
exempt-pr-labels: leave-open COMMENT_BOTH: |-
operations-per-run: 200 This pull request has been marked as blocked and is failing
ascending: true checks for {days} days. It is not ready for inclusion. It is being
ignore-updates: true automatically closed.
EXEMPT_LABEL: "leave-open"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const labelBlocked = process.env.LABEL_BLOCKED || "blocked";
const labelChecks = process.env.LABEL_CHECKS || "pr-check-blocked";
const daysThreshold = parseInt(process.env.DAYS_THRESHOLD || "14", 10);
const exemptLabel = process.env.EXEMPT_LABEL || "leave-open";
const dryRun = process.env.DRY_RUN === "true";
if (daysThreshold < 2) throw new Error(`DAYS_THRESHOLD must be >= 2, got ${daysThreshold}`);
const now = new Date();
const commentTemplates = {
blocked: process.env.COMMENT_BLOCKED,
checks: process.env.COMMENT_CHECKS,
both: process.env.COMMENT_BOTH,
};
for (const [key, val] of Object.entries(commentTemplates)) {
if (!val?.trim()) throw new Error(`Comment template "${key}" is empty or missing`);
}
const prs = await github.paginate(github.rest.pulls.list, {
owner: context.repo.owner,
repo: context.repo.repo,
state: "open",
per_page: 100
});
for (const pr of prs) {
const prLabels = pr.labels.map(l => l.name);
const hasBlocked = prLabels.includes(labelBlocked);
const hasChecks = prLabels.includes(labelChecks);
if (!hasBlocked && !hasChecks) continue;
if (pr.draft) continue;
if (prLabels.includes(exemptLabel)) continue;
const effectiveThreshold = hasChecks
? Math.floor(daysThreshold / 2)
: daysThreshold;
const commentTemplate = hasBlocked && hasChecks
? commentTemplates.both
: hasChecks
? commentTemplates.checks
: commentTemplates.blocked;
try {
const events = await github.paginate(
github.rest.issues.listEventsForTimeline,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
}
);
const relevantLabels = [];
if (hasBlocked) relevantLabels.push(labelBlocked);
if (hasChecks) relevantLabels.push(labelChecks);
const labelEvents = events
.filter(e =>
e.event === "labeled" &&
relevantLabels.includes(e.label?.name)
)
.map(e => new Date(e.created_at));
if (labelEvents.length === 0) continue;
const appliedDate = new Date(
Math.min(...labelEvents.map(d => d.getTime()))
);
const diffDays = (now - appliedDate) / (1000 * 60 * 60 * 24);
if (diffDays >= effectiveThreshold) {
const days = Math.floor(diffDays);
let reason;
if (hasBlocked && hasChecks) {
reason = "marked as blocked and failing checks";
} else if (hasChecks) {
reason = "failing checks";
} else {
reason = "marked as blocked";
}
const msg = `Closing PR ${pr.html_url} because it has been ${reason} for ${days} days`;
if (dryRun) {
console.log(`[DRY RUN] ${msg}`);
} else {
console.log(msg);
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: commentTemplate.replace("{days}", Math.floor(diffDays))
});
} catch (err) {
console.warn(`Failed to comment on PR ${pr.html_url}: ${err.message}`);
}
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
state: "closed"
});
}
}
} catch (err) {
console.error(`Error processing PR ${pr.html_url}: ${err.message}`);
}
}