From e2d499cca69a193bf3a0996426c5b3ccb94b2c97 Mon Sep 17 00:00:00 2001
From: jdx <216188+jdx@users.noreply.github.com>
Date: Sun, 22 Mar 2026 11:02:34 -0500
Subject: [PATCH] ci: add workflow to auto-close stale PRs (#409)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
- Adds a daily workflow that auto-closes PRs inactive for 30+ days
- Skips PRs authored by jdx or labeled `keep-open`
- Includes different close messages depending on CI status (failing vs
passing)
Ported from jdx/mise.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---
> [!NOTE]
> **Low Risk**
> Low risk: adds a standalone GitHub Actions workflow that only closes
eligible open PRs and does not affect application/runtime code.
>
> **Overview**
> Adds a new GitHub Actions workflow (`.github/workflows/pr-closer.yml`)
that runs daily (and manually) to close PRs with no activity for 30+
days.
>
> The job filters out PRs authored by `jdx` or labeled `keep-open`, and
posts a different close comment when CI checks are failing vs passing.
>
> Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
959b5b27b25da4128692186a781052cbe494afc7. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).
---------
Co-authored-by: Claude Opus 4.6 (1M context)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
---
.github/workflows/pr-closer.yml | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
create mode 100644 .github/workflows/pr-closer.yml
diff --git a/.github/workflows/pr-closer.yml b/.github/workflows/pr-closer.yml
new file mode 100644
index 0000000..7c0cc80
--- /dev/null
+++ b/.github/workflows/pr-closer.yml
@@ -0,0 +1,31 @@
+name: pr-closer
+
+on:
+ schedule:
+ - cron: "0 0 * * *" # daily at midnight
+ workflow_dispatch:
+
+jobs:
+ close-stale-prs:
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ steps:
+ - name: Close stale PRs
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ gh pr list -R "${{ github.repository }}" --state open --json number,author,labels,updatedAt,statusCheckRollup --limit 100 | \
+ jq -r '.[] | select(
+ (.updatedAt | fromdateiso8601) < (now - 30*24*60*60) and
+ .author.login != "jdx" and
+ ([.labels[].name] | index("keep-open") | not)
+ ) | [.number, (if (.statusCheckRollup | length > 0) and (.statusCheckRollup | any(.conclusion == "FAILURE" or .conclusion == "failure")) then "failing" else "passing" end)] | @tsv' | \
+ while read -r pr status; do
+ echo "Closing PR #$pr (checks: $status)"
+ if [ "$status" = "failing" ]; then
+ gh pr close "$pr" -R "${{ github.repository }}" -c "This PR has been open for more than 30 days without activity. Note: CI checks were failing, which may be why it wasn't reviewed. Feel free to reopen or create a new PR if you'd like to continue working on this."
+ else
+ gh pr close "$pr" -R "${{ github.repository }}" -c "This PR has been open for more than 30 days without activity. Feel free to reopen or create a new PR if you'd like to continue working on this."
+ fi
+ done