Repository navigation
Cleanup #459
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: Cleanup | |
| # Deletes branches with no activity for 2 months. (The old hourly cron drove a | |
| # since-removed stale-pr job — every one of those 24 runs/day matched no job | |
| # and did nothing.) | |
| # ============================================================================= | |
| # WORKFLOW CONFIGURATION | |
| # ============================================================================= | |
| on: | |
| schedule: | |
| - cron: '41 4 * * *' # daily -> stale-branches | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| # ============================================================================= | |
| # JOBS | |
| # ============================================================================= | |
| jobs: | |
| stale-branches: | |
| name: Delete Stale Branches | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: write | |
| pull-requests: read # open-PR guard below | |
| steps: | |
| - name: Delete branches with no commits for MAX_AGE_DAYS | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| MAX_AGE_DAYS: "60" | |
| run: | | |
| set -o pipefail | |
| CUTOFF=$(date -u -d "$MAX_AGE_DAYS days ago" +%Y-%m-%dT%H:%M:%SZ) | |
| OWNER="${REPO%%/*}" | |
| echo "Deleting branches with no commit since $CUTOFF" | |
| gh api --paginate "repos/$REPO/branches?protected=false&per_page=100" \ | |
| --jq '.[].name' | | |
| while read -r br; do | |
| # The long-lived branches are non-negotiable, protected or not. | |
| case "$br" in main|develop) continue ;; esac | |
| # An API hiccup must SKIP the branch, not delete it: the old | |
| # `last=$(gh api ...)` left last empty on failure, and | |
| # [[ "" < "$CUTOFF" ]] is true — a transient 5xx deleted branches. | |
| if ! last=$(gh api "repos/$REPO/commits/$br" --jq '.commit.committer.date') || [[ -z "$last" ]]; then | |
| echo "Skipping $br (could not resolve last commit)" | |
| continue | |
| fi | |
| [[ "$last" < "$CUTOFF" ]] || continue | |
| # Deleting a branch force-closes its open PRs — age alone is not | |
| # consent for that. Skip (and skip on a failed check, same logic). | |
| if ! open=$(gh api "repos/$REPO/pulls?state=open&head=$OWNER:$br" --jq 'length') || [[ "$open" != "0" ]]; then | |
| echo "Skipping $br (open PR, or PR check failed)" | |
| continue | |
| fi | |
| echo "Deleting $br (last commit $last)" | |
| gh api --silent -X DELETE "repos/$REPO/git/refs/heads/$br" || echo "Failed to delete $br (continuing)" | |
| done |