Auto Close Stale PRs #213
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Auto Close Stale PRs | |
| on: | |
| schedule: | |
| - cron: '0 0 * * *' # Runs daily at midnight UTC | |
| workflow_dispatch: # Allow manual trigger for testing | |
| jobs: | |
| stale-pr-check: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Close stale PRs | |
| uses: actions/stale@v9 | |
| with: | |
| # Skip PRs with Stale Exempt label | |
| any-of-pr-labels: 'Stale Exempt' | |
| # This workflow is PR-only; never process issues. | |
| days-before-issue-stale: -1 | |
| days-before-issue-close: -1 | |
| # Only consider PRs with these labels as candidates for staleness | |
| only-pr-labels: 'PR awaiting response from author,PR awaiting changes from author' | |
| # Ignore draft PRs | |
| exempt-draft-pr: true | |
| # Days before marking as stale | |
| days-before-stale: 7 | |
| # Days before closing after being marked stale | |
| days-before-close: 7 | |
| # Label to apply when marking as stale | |
| stale-pr-label: 'PR Stale' | |
| # Message when marking as stale | |
| stale-pr-message: | | |
| Hi @${author}! 👋 | |
| This pull request has been awaiting your action for 7 days. | |
| If you're still working on this, please let us know by commenting or pushing a commit. Otherwise, this PR may be closed automatically in another 7 days to keep our repository tidy. | |
| Thank you for your contribution! 💛 | |
| # Message when closing | |
| close-pr-message: | | |
| Hi @${author}! 👋 | |
| This pull request has been inactive for 7 days after being marked as stale. | |
| We're closing it to keep our repository organized, but feel free to reopen it if you decide to continue working on it! | |
| Thank you for your understanding and for contributing to the project. 💛 | |
| # Remove stale label when PR is updated | |
| remove-pr-stale-when-updated: true | |
| # Operations per run | |
| operations-per-run: 30 | |
| - name: Clean up labels on closed PRs | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| // Get PRs closed in the last 25 hours | |
| const twentyFiveHoursAgo = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString(); | |
| const { data: prs } = await github.rest.pulls.list({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: "closed", | |
| sort: "updated", | |
| direction: "desc", | |
| per_page: 100 | |
| }); | |
| const awaitingLabels = ["PR awaiting response from author", "PR awaiting changes from author"]; | |
| for (const pr of prs) { | |
| // Only process PRs closed in the last 25 hours | |
| const closedAt = new Date(pr.closed_at); | |
| if (closedAt < new Date(twentyFiveHoursAgo)) continue; | |
| const labels = pr.labels.map(l => l.name); | |
| const hasAwaitingLabel = awaitingLabels.some(label => labels.includes(label)); | |
| // Only process PRs that have awaiting labels (indicating they were handled by stale action) | |
| if (!hasAwaitingLabel) continue; | |
| console.log(`Processing closed PR #${pr.number} (closed at ${closedAt}) - has awaiting labels`); | |
| // Remove all existing labels | |
| for (const label of labels) { | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| name: label | |
| }); | |
| console.log(`Removed "${label}" from PR #${pr.number}`); | |
| } catch (error) { | |
| console.log(`Could not remove "${label}" from PR #${pr.number}: ${error.message}`); | |
| } | |
| } | |
| // Add the "PR closed due to inactivity" label | |
| try { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| labels: ["PR closed due to inactivity"] | |
| }); | |
| console.log(`Added "PR closed due to inactivity" to PR #${pr.number}`); | |
| } catch (error) { | |
| console.log(`Could not add label to PR #${pr.number}: ${error.message}`); | |
| } | |
| } |