Files
2025-11-29 18:18:18 +08:00

8.7 KiB

GitHub CLI Best Practices

Shell scripting patterns, bulk operations, and automation strategies for gh CLI.

Output Formats and Processing

JSON Output for Programmatic Parsing

# Default: Human-readable text
gh pr list

# JSON output for programmatic parsing
gh pr list --json number,title,state,author

# JSON with jq processing
gh pr list --json number,title | jq '.[] | select(.title | contains("bug"))'

# Template output for custom formatting
gh pr list --template '{{range .}}{{.number}}: {{.title}}{{"\n"}}{{end}}'

Field Selection

# Select specific fields
gh pr view 123 --json number,title,state,reviews

# All available fields
gh pr view 123 --json

# Nested field extraction
gh pr list --json number,author | jq '.[].author.login'

Pagination Strategies

Controlling Result Limits

# Limit results (default is usually 30)
gh pr list --limit 50

# Show all results (use carefully)
gh pr list --limit 999

# Paginate manually
gh pr list --limit 100 --page 1
gh pr list --limit 100 --page 2

Processing Large Result Sets

# Get all PRs in batches
for page in {1..10}; do
  gh pr list --limit 100 --page $page --json number,title
done

# Stop when no more results
page=1
while true; do
  results=$(gh pr list --limit 100 --page $page --json number)
  if [ "$results" == "[]" ]; then break; fi
  echo "$results"
  ((page++))
done

Error Handling and Reliability

Exit Code Checking

# Check exit codes
gh pr merge 123 && echo "Success" || echo "Failed"

# Capture exit code
gh pr create --title "Title" --body "Body"
exit_code=$?
if [ $exit_code -eq 0 ]; then
  echo "PR created successfully"
else
  echo "PR creation failed with code $exit_code"
fi

Error Output Handling

# Separate stdout and stderr
gh pr list > success.log 2> error.log

# Redirect errors to stdout
gh pr list 2>&1 | tee combined.log

# Suppress errors
gh pr view 999 2>/dev/null || echo "PR not found"

Retry Logic

# Simple retry
for i in {1..3}; do
  gh api repos/{owner}/{repo}/pulls && break
  echo "Retry $i failed, waiting..."
  sleep 5
done

# Exponential backoff
attempt=1
max_attempts=5
delay=1

while [ $attempt -le $max_attempts ]; do
  if gh pr create --title "Title" --body "Body"; then
    break
  fi
  echo "Attempt $attempt failed, retrying in ${delay}s..."
  sleep $delay
  delay=$((delay * 2))
  attempt=$((attempt + 1))
done

Bulk Operations

Operating on Multiple Items

# Close all PRs with specific label
gh pr list --label "wip" --json number -q '.[].number' | \
  xargs -I {} gh pr close {}

# Add label to multiple issues
gh issue list --state open --json number -q '.[].number' | \
  xargs -I {} gh issue edit {} --add-label "needs-triage"

# Approve multiple PRs
gh pr list --author username --json number -q '.[].number' | \
  xargs -I {} gh pr review {} --approve

Parallel Execution

# Process items in parallel (GNU parallel)
gh pr list --json number -q '.[].number' | \
  parallel -j 4 gh pr view {}

# Xargs parallel execution
gh pr list --json number -q '.[].number' | \
  xargs -P 4 -I {} gh pr checks {}

Batch Processing with Confirmation

# Confirm before bulk operation
gh pr list --label "old" --json number,title | \
  jq -r '.[] | "\(.number): \(.title)"' | \
  while read -r line; do
    echo "Close PR $line? (y/n)"
    read -r answer
    if [ "$answer" == "y" ]; then
      pr_num=$(echo "$line" | cut -d: -f1)
      gh pr close "$pr_num"
    fi
  done

Enterprise GitHub Patterns

Working with GitHub Enterprise

# Authenticate with enterprise hostname
gh auth login --hostname github.enterprise.com

# Set environment variable for enterprise
export GH_HOST=github.enterprise.com
gh pr list

# Use with specific host
gh pr list --hostname github.enterprise.com

# Check current authentication
gh auth status

Switching Between Instances

# Switch between GitHub.com and Enterprise
gh auth switch

# Use specific auth token
GH_TOKEN=ghp_enterprise_token gh pr list --hostname github.enterprise.com

Automation and Scripting

Capturing Output

# Capture PR number
PR_NUMBER=$(gh pr create --title "Title" --body "Body" | grep -oP '\d+$')
echo "Created PR #$PR_NUMBER"

# Capture JSON and parse
pr_data=$(gh pr view 123 --json number,title,state)
pr_state=$(echo "$pr_data" | jq -r '.state')

# Capture and validate
if output=$(gh pr merge 123 2>&1); then
  echo "Merged successfully"
else
  echo "Merge failed: $output"
fi

Conditional Operations

# Check PR status before merging
pr_state=$(gh pr view 123 --json state -q '.state')
if [ "$pr_state" == "OPEN" ]; then
  gh pr merge 123 --squash
fi

# Check CI status
checks=$(gh pr checks 123 --json state -q '.[].state')
if echo "$checks" | grep -q "FAILURE"; then
  echo "CI checks failed, cannot merge"
  exit 1
fi

Workflow Automation

#!/bin/bash
# Automated PR workflow

# Create feature branch
git checkout -b feature/new-feature

# Make changes and commit
# ...

# Push and create PR
git push -u origin feature/new-feature
PR_NUM=$(gh pr create \
  --title "feat: New feature" \
  --body "Description of feature" \
  --label "enhancement" \
  | grep -oP '\d+$')

# Wait for CI
echo "Waiting for CI checks..."
while true; do
  status=$(gh pr checks "$PR_NUM" --json state -q '.[].state' | grep -v "SUCCESS")
  if [ -z "$status" ]; then
    echo "All checks passed!"
    break
  fi
  sleep 30
done

# Auto-merge if checks pass
gh pr merge "$PR_NUM" --squash --auto

Configuration and Customization

Setting Defaults

# Set default repository
gh repo set-default owner/repo

# Configure editor
gh config set editor vim

# Configure browser
gh config set browser firefox

# Set Git protocol preference
gh config set git_protocol ssh  # or https

# View current configuration
gh config list

Environment Variables

# GitHub token
export GH_TOKEN=ghp_your_token

# GitHub host
export GH_HOST=github.enterprise.com

# Default repository
export GH_REPO=owner/repo

# Pager
export GH_PAGER=less

# No prompts (for automation)
export GH_NO_UPDATE_NOTIFIER=1

Performance Optimization

Reducing API Calls

# Cache frequently used data
pr_list=$(gh pr list --json number,title,state)
echo "$pr_list" | jq '.[] | select(.state == "OPEN")'
echo "$pr_list" | jq '.[] | select(.state == "MERGED")'

# Use single API call for multiple fields
gh pr view 123 --json number,title,state,reviews,comments

Selective Field Loading

# Only fetch needed fields
gh pr list --json number,title  # Fast

# vs. fetching all fields
gh pr list --json  # Slower

Debugging and Troubleshooting

Verbose Output

# Enable debug logging
GH_DEBUG=1 gh pr list

# API logging
GH_DEBUG=api gh pr create --title "Test"

# Full HTTP trace
GH_DEBUG=api,http gh api repos/{owner}/{repo}

Testing API Calls

# Test API endpoint
gh api repos/{owner}/{repo}/pulls

# Test with custom headers
gh api repos/{owner}/{repo}/pulls \
  -H "Accept: application/vnd.github.v3+json"

# Test pagination
gh api repos/{owner}/{repo}/pulls --paginate

Best Practices Summary

Do's

Use JSON output for programmatic parsing Handle errors with proper exit code checking Implement retries for network operations Cache results when making multiple queries Use bulk operations for efficiency Set appropriate limits to avoid rate limiting Validate input before operations Log operations for audit trail

Don'ts

Don't hardcode credentials - Use environment variables or gh auth Don't ignore errors - Always check exit codes Don't fetch all fields - Select only what you need Don't skip rate limit checks - Monitor API usage Don't run destructive operations without confirmation Don't assume unlimited results - Always paginate Don't mix automation with interactive - Use GH_NO_UPDATE_NOTIFIER=1


Common Patterns

Create, Wait, Merge Pattern

# Create PR
PR_NUM=$(gh pr create --title "Feature" --body "Description" | grep -oP '\d+$')

# Wait for checks
gh pr checks "$PR_NUM" --watch

# Merge when ready
gh pr merge "$PR_NUM" --squash

Search and Process Pattern

# Find and process matching items
gh pr list --json number,title | \
  jq -r '.[] | select(.title | contains("bug")) | .number' | \
  while read -r pr; do
    gh pr edit "$pr" --add-label "bug"
  done

Batch Approval Pattern

# Review and approve multiple PRs
gh pr list --author trusted-user --json number -q '.[].number' | \
  while read -r pr; do
    gh pr diff "$pr"
    gh pr review "$pr" --approve --body "LGTM"
  done