Commit deb276faa6b1

Vincent Demeester <vincent@sbr.pm>
2025-12-04 23:16:20
feat(tools): add direct PR restart mode to gh-restart-failed
- Enable non-interactive restart via owner/repo#123 syntax - Remove fzf dependency when PR number is specified directly - Maintain backward compatibility with interactive fzf mode - Update GitHub skill documentation with usage examples Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 7c9585d
Changed files (2)
dots
.config
claude
skills
GitHub
tools
gh-restart-failed
dots/.config/claude/skills/GitHub/workflows/RestartChecks.md
@@ -218,28 +218,54 @@ fi
 **Interactive tool for restarting failed checks across multiple PRs:**
 
 ```bash
-# List and restart failed checks interactively
+# Interactive mode: List and restart failed checks with fzf
 gh-restart-failed
 
-# Use specific repository
+# Direct mode: Restart specific PR without interactive selection
+gh-restart-failed owner/repo#123
+
+# Direct mode with current repository
+gh-restart-failed#42
+
+# Use specific repository (interactive)
 gh-restart-failed owner/repo
 
 # Ignore specific workflows (Label Checker ignored by default)
 gh-restart-failed -i "build" -i "test"
 
-# Filter by PR labels
+# Filter by PR labels (interactive mode)
 gh-restart-failed -l "bug" -l "enhancement"
+
+# Direct mode with options
+gh-restart-failed -i "Label Checker" owner/repo#123
 ```
 
 **Features:**
-- Interactive fzf interface with multi-select
+- **Two modes**: Interactive (fzf) or direct (specify PR number)
+- **Direct syntax**: `owner/repo#123` to skip interactive selection
+- Interactive fzf interface with multi-select (when no PR specified)
 - Preview failed checks before restarting
-- Handles multiple PRs at once
+- Handles multiple PRs at once (interactive mode)
 - Filters out old runs (>1 month)
 - Color-coded output with status
+- No fzf dependency required for direct mode
 
 **Location**: `~/src/home/tools/gh-restart-failed/`
 
+**Usage Modes:**
+
+1. **Interactive** (requires fzf): Browse and select PRs interactively
+   ```bash
+   gh-restart-failed
+   gh-restart-failed owner/repo
+   ```
+
+2. **Direct** (no fzf needed): Immediately restart specific PR
+   ```bash
+   gh-restart-failed owner/repo#123
+   gh-restart-failed#42  # current repo
+   ```
+
 ## Custom Tool: Intelligent Restart (Planned)
 
 For more sophisticated restart logic:
tools/gh-restart-failed/gh-restart-failed.sh
@@ -12,7 +12,7 @@ NC='\033[0m' # No Color
 # Help message
 usage() {
     cat <<EOF
-Usage: gh-restart-failed [OPTIONS] [REPOSITORY]
+Usage: gh-restart-failed [OPTIONS] [REPOSITORY[#PR_NUMBER]]
 
 List pull requests with failed checks and restart selected workflows.
 
@@ -24,18 +24,20 @@ Options:
 Arguments:
     REPOSITORY    Optional repository in OWNER/REPO format or path to local repo.
                   If not provided, uses the current directory's repository.
+                  Can include #PR_NUMBER to directly restart a specific PR (skips interactive selection).
 
 Dependencies:
     - gh (GitHub CLI)
-    - fzf (fuzzy finder)
+    - fzf (fuzzy finder, only needed for interactive mode)
     - jq (JSON processor)
 
 Note:
     By default, "Label Checker" workflows are ignored. Use -i to add more patterns.
 
 Examples:
-    gh-restart-failed                                    # Use current repository
-    gh-restart-failed owner/repo                         # Use specific GitHub repository
+    gh-restart-failed                                    # Use current repository (interactive)
+    gh-restart-failed owner/repo#123                     # Directly restart PR #123 in owner/repo
+    gh-restart-failed owner/repo                         # Use specific GitHub repository (interactive)
     gh-restart-failed -i "build" -i "test"              # Ignore build and test workflows
     gh-restart-failed -l "bug" -l "enhancement"         # Only show PRs with bug OR enhancement labels
     gh-restart-failed /path/to/repo                     # Use repository at path
@@ -48,7 +50,7 @@ EOF
 check_dependencies() {
     local missing=()
 
-    for cmd in gh fzf jq; do
+    for cmd in gh jq; do
         if ! command -v "$cmd" &> /dev/null; then
             missing+=("$cmd")
         fi
@@ -67,6 +69,7 @@ LABEL_FILTERS=()
 
 # Parse arguments
 REPO_ARG=""
+PR_NUMBER=""
 while [[ $# -gt 0 ]]; do
     case $1 in
         -h|--help)
@@ -96,6 +99,11 @@ while [[ $# -gt 0 ]]; do
             ;;
         *)
             REPO_ARG="$1"
+            # Check if it contains #PR_NUMBER
+            if [[ "$REPO_ARG" =~ ^(.+)#([0-9]+)$ ]]; then
+                REPO_ARG="${BASH_REMATCH[1]}"
+                PR_NUMBER="${BASH_REMATCH[2]}"
+            fi
             shift
             ;;
     esac
@@ -103,6 +111,13 @@ done
 
 check_dependencies
 
+# Check fzf only if in interactive mode (no PR_NUMBER specified)
+if [ -z "$PR_NUMBER" ] && ! command -v fzf &> /dev/null; then
+    echo -e "${RED}Error: fzf is required for interactive mode${NC}" >&2
+    echo "Please install fzf or specify a PR number directly (e.g., owner/repo#123)" >&2
+    exit 1
+fi
+
 # Determine repository context
 REPO_FLAG=()
 if [ -n "$REPO_ARG" ]; then
@@ -125,56 +140,78 @@ if [ ${#LABEL_FILTERS[@]} -gt 0 ]; then
     echo -e "${YELLOW}Filtering PRs with labels: ${LABEL_FILTERS[*]}${NC}" >&2
 fi
 
-# Get all open PRs with their check status
-echo -e "${BLUE}Fetching pull requests...${NC}" >&2
+# If PR_NUMBER is specified, skip interactive selection
+if [ -n "$PR_NUMBER" ]; then
+    echo -e "${BLUE}Fetching PR #$PR_NUMBER...${NC}" >&2
 
-# Build label filter arguments for gh pr list
-LABEL_ARGS=()
-for label in "${LABEL_FILTERS[@]}"; do
-    LABEL_ARGS+=(--label "$label")
-done
+    # Fetch specific PR information
+    pr_info=$(gh pr view "${REPO_FLAG[@]}" "$PR_NUMBER" \
+        --json number,title,headRefName,author \
+        2>/dev/null)
 
-# Fetch PRs with detailed check information
-prs_json=$(gh pr list "${REPO_FLAG[@]}" \
-    "${LABEL_ARGS[@]}" \
-    --json number,title,headRefName,author,statusCheckRollup \
-    --limit 100)
+    if [ -z "$pr_info" ]; then
+        echo -e "${RED}Error: PR #$PR_NUMBER not found${NC}" >&2
+        exit 1
+    fi
 
-# Filter PRs with failed checks and format for display
-failed_prs=$(echo "$prs_json" | jq -r '
-    .[] |
-    select(.statusCheckRollup // [] | any(.conclusion == "FAILURE" or .conclusion == "TIMED_OUT" or .conclusion == "STARTUP_FAILURE" or .conclusion == "ACTION_REQUIRED")) |
-    {
-        number: .number,
-        title: .title,
-        branch: .headRefName,
-        author: .author.login,
-        failed_checks: [.statusCheckRollup[] | select(.conclusion == "FAILURE" or .conclusion == "TIMED_OUT" or .conclusion == "STARTUP_FAILURE" or .conclusion == "ACTION_REQUIRED")]
-    } |
-    "#\(.number) | \(.title) | @\(.author) | \(.branch) | \(.failed_checks | length) failed"
-')
+    pr_title=$(echo "$pr_info" | jq -r '.title')
+    pr_branch=$(echo "$pr_info" | jq -r '.headRefName')
+    pr_author=$(echo "$pr_info" | jq -r '.author.login')
 
-if [ -z "$failed_prs" ]; then
-    echo -e "${GREEN}No pull requests with failed checks found!${NC}"
-    exit 0
-fi
+    # Format as if selected from interactive mode
+    selected_prs="#$PR_NUMBER | $pr_title | @$pr_author | $pr_branch | direct"
+else
+    # Interactive mode: Get all open PRs with their check status
+    echo -e "${BLUE}Fetching pull requests...${NC}" >&2
 
-echo -e "${YELLOW}Found pull requests with failed checks:${NC}" >&2
-echo ""
+    # Build label filter arguments for gh pr list
+    LABEL_ARGS=()
+    for label in "${LABEL_FILTERS[@]}"; do
+        LABEL_ARGS+=(--label "$label")
+    done
 
-# Use fzf to select PRs
-selected_prs=$(echo "$failed_prs" | fzf \
-    --multi \
-    --ansi \
-    --header="Select pull requests to restart failed workflows (TAB to select multiple, ENTER to confirm)" \
-    --preview="pr_number=\$(echo {} | cut -d'|' -f1 | tr -d '# '); gh pr checks ${REPO_FLAG[*]} \"\$pr_number\" 2>/dev/null | grep -E '(fail|FAILURE|×)' || echo 'Loading...'" \
-    --preview-window=right:60%:wrap \
-    --bind='ctrl-/:toggle-preview' \
-    --height=80%)
+    # Fetch PRs with detailed check information
+    prs_json=$(gh pr list "${REPO_FLAG[@]}" \
+        "${LABEL_ARGS[@]}" \
+        --json number,title,headRefName,author,statusCheckRollup \
+        --limit 100)
 
-if [ -z "$selected_prs" ]; then
-    echo -e "${YELLOW}No pull requests selected.${NC}"
-    exit 0
+    # Filter PRs with failed checks and format for display
+    failed_prs=$(echo "$prs_json" | jq -r '
+        .[] |
+        select(.statusCheckRollup // [] | any(.conclusion == "FAILURE" or .conclusion == "TIMED_OUT" or .conclusion == "STARTUP_FAILURE" or .conclusion == "ACTION_REQUIRED")) |
+        {
+            number: .number,
+            title: .title,
+            branch: .headRefName,
+            author: .author.login,
+            failed_checks: [.statusCheckRollup[] | select(.conclusion == "FAILURE" or .conclusion == "TIMED_OUT" or .conclusion == "STARTUP_FAILURE" or .conclusion == "ACTION_REQUIRED")]
+        } |
+        "#\(.number) | \(.title) | @\(.author) | \(.branch) | \(.failed_checks | length) failed"
+    ')
+
+    if [ -z "$failed_prs" ]; then
+        echo -e "${GREEN}No pull requests with failed checks found!${NC}"
+        exit 0
+    fi
+
+    echo -e "${YELLOW}Found pull requests with failed checks:${NC}" >&2
+    echo ""
+
+    # Use fzf to select PRs
+    selected_prs=$(echo "$failed_prs" | fzf \
+        --multi \
+        --ansi \
+        --header="Select pull requests to restart failed workflows (TAB to select multiple, ENTER to confirm)" \
+        --preview="pr_number=\$(echo {} | cut -d'|' -f1 | tr -d '# '); gh pr checks ${REPO_FLAG[*]} \"\$pr_number\" 2>/dev/null | grep -E '(fail|FAILURE|×)' || echo 'Loading...'" \
+        --preview-window=right:60%:wrap \
+        --bind='ctrl-/:toggle-preview' \
+        --height=80%)
+
+    if [ -z "$selected_prs" ]; then
+        echo -e "${YELLOW}No pull requests selected.${NC}"
+        exit 0
+    fi
 fi
 
 echo ""