Long-running projects accumulate outdated issues: bugs for old versions, irrelevant features, test tickets. Manual deletion takes significant time. The script automatically finds and deletes (or marks) old issues by specified labels.
💡 Script uses dry-run by default - shows what would be deleted without actual deletion.
📦 Script: delete-issues.sh Full code #!/bin/bash # Delete old GitHub Issues by labels and date # Usage: ./delete-issues.sh [--execute] set -euo pipefail # === CONFIG === REPO="owner/repo" # Repository in owner/repo format LABELS='label1,label2,label3' # Labels to filter (comma-separated) CUTOFF="2025-12-31T23:59:59Z" # Delete issues created BEFORE this date DRY_RUN=true # true = preview only, false = actually delete # Parse arguments if [[ "${1:-}" == "--execute" ]]; then DRY_RUN=false echo "⚠️ Mode: ACTUAL DELETION" else echo "ℹ️ Mode: DRY RUN (nothing will be deleted)" fi echo "🔍 Searching issues in $REPO with labels: $LABELS, created before $CUTOFF" echo "---" # Fetch and filter issues gh issue list --repo "$REPO" --state all --limit 1000 \ --json number,title,createdAt,labels,url | \ jq -r --arg labels "$LABELS" --arg cutoff "$CUTOFF" ' ($labels | split(",")) as $label_array | .[] | select(.createdAt < $cutoff) | select(.labels | map(.name) | any(. as $l | $label_array | index($l))) | "\(.number)|\(.title)|\(.url)" ' | \ while IFS='|' read -r number title url; do if [[ "$DRY_RUN" == "true" ]]; then echo "[DRY RUN] ##$number - $title" echo " $url" else echo "🗑 Deleting ##$number - $title" gh issue delete "$REPO" "$number" --yes sleep 1 # Small pause to avoid rate limits fi done echo "---" echo "✅ Done" Install dependencies # GitHub CLI # Windows (winget): winget install GitHub.cli # Linux (Ubuntu/Debian): sudo apt install gh # macOS (Homebrew): brew install gh # Authenticate gh auth login # jq (JSON processor) # Windows (winget): winget install jq.jq # Linux: sudo apt install jq # macOS: brew install jq Run # Make script executable chmod +x delete-issues.sh # DRY RUN (safe mode - preview only) ./delete-issues.sh # ACTUAL DELETION (add --execute flag) ./delete-issues.sh --execute 🔍 How it works Step-by-step breakdown Step Command What it does 1 gh issue list --json ... Fetches issues as JSON with fields: number, title, date, labels, URL 2 jq -r ... Filters: date < cutoff AND has at least one of specified labels 3 while read ... Processes each matching issue 4 gh issue delete Deletes issue (only if DRY_RUN=false) jq filter logic ($labels | split(",")) as $label_array | # Split label string into array .[] | # For each issue select(.createdAt < $cutoff) | # Only if created before cutoff select( .labels | map(.name) | # Get list of label names any(. as $l | $label_array | index($l)) # Check if any matches our labels ) | "\(.number)|\(.title)|\(.url)" # Format output Rationale:
...