From dbf87126845c6f220fc524c2b8e27a5c5994bf52 Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Sun, 17 May 2026 15:37:19 +0000 Subject: [PATCH] chore(ci): close failing or conflicted PRs sooner --- .github/workflows/pr-closer.yml | 47 ++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pr-closer.yml b/.github/workflows/pr-closer.yml index 7c0cc80..f8e2e58 100644 --- a/.github/workflows/pr-closer.yml +++ b/.github/workflows/pr-closer.yml @@ -5,27 +5,50 @@ on: - cron: "0 0 * * *" # daily at midnight workflow_dispatch: +concurrency: + group: pr-closer + cancel-in-progress: true + jobs: close-stale-prs: runs-on: ubuntu-latest permissions: pull-requests: write + checks: read + statuses: read steps: - name: Close stale PRs env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} 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." + set -o pipefail + CUTOFF=$(date -u -d '6 days ago' +%Y-%m-%d) + gh pr list -R "$REPO" --state open --search "updated:<$CUTOFF -author:jdx -label:keep-open draft:false" --json number,mergeStateStatus,statusCheckRollup --limit 500 | \ + jq -r ' + def failed_check: + (.statusCheckRollup | length > 0) and + ([.statusCheckRollup // [] | .[] | ((.conclusion // .state // "") | ascii_upcase)] | any(. == "FAILURE" or . == "ERROR" or . == "TIMED_OUT" or . == "ACTION_REQUIRED")); + + .[] + | failed_check as $failed + | ([.statusCheckRollup // [] | .[] | ((.conclusion // .state // "") | ascii_upcase)] | any(. == "CANCELLED")) as $cancelled + | (.mergeStateStatus == "DIRTY") as $conflicted + | (.mergeStateStatus == "UNKNOWN") as $unknown + | if $failed and $conflicted then [.number, "failing checks and merge conflicts"] + elif $failed then [.number, "failing checks"] + elif $conflicted then [.number, "merge conflicts"] + elif $cancelled then [.number, "cancelled checks", "warn"] + elif $unknown then [.number, "unknown merge state", "warn"] + else empty + end + | @tsv + ' | \ + while IFS=$'\t' read -r pr reason action; do + if [ "$action" = "warn" ]; then + echo "Skipping PR #$pr ($reason)" + continue fi + echo "Closing PR #$pr ($reason)" + gh pr close "$pr" -R "$REPO" -c "This PR has been inactive for at least 7 days and currently has $reason. Feel free to reopen or create a new PR if you'd like to continue working on this." || echo "Warning: failed to close PR #$pr, skipping" done