From c700b928e4c42dbb7f5807cd4de81ea85c44aca4 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Mon, 26 Jan 2026 15:38:34 -0500 Subject: [PATCH] ci: add stale pr job --- .github/workflows/close-stale-prs.yml | 78 +++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 .github/workflows/close-stale-prs.yml diff --git a/.github/workflows/close-stale-prs.yml b/.github/workflows/close-stale-prs.yml new file mode 100644 index 0000000000..a9a0502ea5 --- /dev/null +++ b/.github/workflows/close-stale-prs.yml @@ -0,0 +1,78 @@ +name: Close stale PRs + +on: + workflow_dispatch: + schedule: + - cron: "0 6 * * *" + +permissions: + contents: read + issues: write + pull-requests: write + +jobs: + close-stale-prs: + runs-on: ubuntu-latest + steps: + - name: Close inactive PRs + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const DAYS_INACTIVE = 60 + const cutoff = new Date(Date.now() - DAYS_INACTIVE * 24 * 60 * 60 * 1000) + const { owner, repo } = context.repo + const dryRun = false + const stalePrs = [] + + core.info(`Dry run mode: ${dryRun}`) + + const prs = await github.paginate(github.rest.pulls.list, { + owner, + repo, + state: "open", + per_page: 100, + sort: "updated", + direction: "asc", + }) + + for (const pr of prs) { + const lastUpdated = new Date(pr.updated_at) + if (lastUpdated > cutoff) { + core.info(`PR ${pr.number} is fresh`) + continue + } + + stalePrs.push(pr) + } + + if (!stalePrs.length) { + core.info("No stale pull requests found.") + return + } + + for (const pr of stalePrs) { + const issue_number = pr.number + const closeComment = `Closing this pull request because it has had no updates for more than ${DAYS_INACTIVE} days. If you plan to continue working on it, feel free to reopen or open a new PR.` + + if (dryRun) { + core.info(`[dry-run] Would close PR #${issue_number} from ${pr.user.login}`) + continue + } + + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body: closeComment, + }) + + await github.rest.pulls.update({ + owner, + repo, + pull_number: issue_number, + state: "closed", + }) + + core.info(`Closed PR #${issue_number} from ${pr.user.login}`) + }