name: Cancel Unfinished PR Runs on: workflow_dispatch: inputs: workflows: description: 'Space-separated list of workflow filenames to cancel' required: true type: string default: 'pr-test.yml' include_high_priority: description: 'Also cancel runs from high-priority PRs' required: false type: boolean default: false permissions: actions: write # Needed to cancel runs contents: read # Needed to read repo info pull-requests: read # needed for gh pr view (labels) jobs: cancel-unfinished-pr-runs: runs-on: ubuntu-latest steps: - name: Install GitHub CLI run: sudo apt-get install -y gh jq - name: Cancel unfinished PR-associated runs (skip high-priority PRs) env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} WORKFLOWS: ${{ github.event.inputs.workflows || 'pr-test.yml' }} INCLUDE_HIGH_PRIORITY: ${{ github.event.inputs.include_high_priority || 'false' }} shell: bash run: | set -euo pipefail # Read the space-separated string from the input into a bash array read -r -a WORKFLOW_FILES <<< "${WORKFLOWS}" echo "Targeting ${#WORKFLOW_FILES[@]} workflow(s): ${WORKFLOWS}" echo "" for workflow_file in "${WORKFLOW_FILES[@]}"; do echo "=========================================" echo "Workflow: $workflow_file" echo "=========================================" # Get all unfinished runs all_runs=$(gh run list \ --repo "$REPO" \ --workflow "$workflow_file" \ --json databaseId,status,event,url,createdAt \ --limit 1000 \ | jq -c '.[] | select(.status=="queued" or .status=="waiting" or .status=="in_progress")') if [ -z "$all_runs" ]; then echo "✅ No unfinished runs found" echo "" continue fi # Count runs by event type total_runs=$(echo "$all_runs" | wc -l) pr_runs=$(echo "$all_runs" | jq -s '[.[] | select(.event=="pull_request")] | length') other_runs=$(echo "$all_runs" | jq -s '[.[] | select(.event!="pull_request")] | length') echo "📊 Summary: $total_runs unfinished runs ($pr_runs PR-related, $other_runs other)" echo "" # Process non-PR runs first if [ "$other_runs" -gt 0 ]; then echo "--- Non-PR Runs ---" echo "$all_runs" | jq -c 'select(.event!="pull_request")' | while read -r run; do run_url=$(echo "$run" | jq -r '.url') run_event=$(echo "$run" | jq -r '.event') run_status=$(echo "$run" | jq -r '.status') echo " • $run_event ($run_status): $run_url" done echo "" fi # Process PR runs if [ "$pr_runs" -gt 0 ]; then echo "--- PR Runs (checking for cancellation) ---" echo "$all_runs" | jq -c 'select(.event=="pull_request")' | while read -r run; do run_id=$(echo "$run" | jq -r '.databaseId') run_url=$(echo "$run" | jq -r '.url') run_status=$(echo "$run" | jq -r '.status') echo "" echo "Run ($run_status): $run_url" # Fetch full run details to get head repository and branch info run_details=$(gh api -H "Accept: application/vnd.github+json" \ "repos/$REPO/actions/runs/$run_id" 2>/dev/null || true) if [ -z "$run_details" ]; then echo " ⚠️ Could not fetch run details, skipping" continue fi # Get head owner and branch (works for both fork and non-fork PRs) head_owner=$(echo "$run_details" | jq -r '.head_repository.owner.login // empty') head_branch=$(echo "$run_details" | jq -r '.head_branch // empty') if [ -z "$head_owner" ] || [ -z "$head_branch" ]; then echo " ⚠️ Missing head info, skipping" continue fi echo " Branch: ${head_owner}:${head_branch}" # Find PR by searching with head=owner:branch pr_number=$(gh api -H "Accept: application/vnd.github+json" \ "repos/$REPO/pulls?state=open&head=${head_owner}:${head_branch}" \ --jq '.[0].number // empty' 2>/dev/null || true) if [ -z "$pr_number" ]; then echo " ⚠️ No open PR found, skipping" continue fi pr_url="https://github.com/$REPO/pull/$pr_number" echo " PR: $pr_url" # Check for high priority label labels=$(gh pr view "$pr_number" --repo "$REPO" --json labels \ | jq -r '.labels[].name' 2>/dev/null || true) if echo "$labels" | grep -Fxq "bypass-maintenance"; then echo " 🛑 Skipping (bypass-maintenance label, never cancelled)" continue fi if echo "$labels" | grep -Fxq "high priority"; then if [ "$INCLUDE_HIGH_PRIORITY" != "true" ]; then echo " 🛑 Skipping (high priority label)" continue fi echo " ⚠️ High priority PR, but include_high_priority is enabled" fi echo " 🚫 Cancelling..." gh run cancel "$run_id" --repo "$REPO" || echo " ⚠️ Cancellation failed" done fi echo "" done echo "=========================================" echo "✅ Processing complete" echo "========================================="