Skip to content

Stale PR Management #67

Stale PR Management

Stale PR Management #67

Workflow file for this run

name: Stale PR Management
on:
schedule:
# Run daily at 00:00 UTC
- cron: '0 0 * * *'
workflow_dispatch: # Allow manual trigger
permissions:
pull-requests: write
issues: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- name: Mark and close stale PRs
uses: actions/github-script@v7
with:
script: |
const now = new Date();
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
const fourteenDaysAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000);
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
// Get all open PRs
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
per_page: 100,
});
console.log(`Found ${prs.length} open PRs`);
for (const pr of prs) {
const updatedAt = new Date(pr.updated_at);
const labels = pr.labels.map(l => l.name);
const prNumber = pr.number;
// Skip draft PRs
if (pr.draft) {
console.log(`PR #${prNumber} is a draft, skipping`);
continue;
}
// 30+ days: Close PR
if (updatedAt < thirtyDaysAgo) {
console.log(`PR #${prNumber} is 30+ days old, closing`);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: '🔒 This PR has been automatically closed due to 30 days of inactivity. If you would like to continue working on this, please reopen the PR and update it.',
});
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
state: 'closed',
});
continue;
}
// 14+ days: Add warning comment (if not already warned)
if (updatedAt < fourteenDaysAgo) {
console.log(`PR #${prNumber} is 14+ days old, adding warning`);
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const alreadyWarned = comments.some(c => c.body.includes('This PR will be closed in 16 days'));
if (!alreadyWarned) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: '⚠️ This PR has been inactive for 14 days. This PR will be closed in 16 days if there is no activity. Please update the PR or add a comment to keep it active.',
});
}
}
// 7+ days: Add stale label
if (updatedAt < sevenDaysAgo && !labels.includes('stale')) {
console.log(`PR #${prNumber} is 7+ days old, marking as stale`);
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: ['stale'],
});
}
// Remove stale label if PR was recently updated
if (updatedAt >= sevenDaysAgo && labels.includes('stale')) {
console.log(`PR #${prNumber} was recently updated, removing stale label`);
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: 'stale',
});
} catch (error) {
console.log(`Could not remove stale label: ${error.message}`);
}
}
}
console.log('Stale PR management completed');