Skip to content

Instantly share code, notes, and snippets.

@the21st
Last active July 8, 2025 14:48
Show Gist options
  • Select an option

  • Save the21st/aeab60a5b4fb4e3d763f4a6762009d0f to your computer and use it in GitHub Desktop.

Select an option

Save the21st/aeab60a5b4fb4e3d763f4a6762009d0f to your computer and use it in GitHub Desktop.
prwatch – a script that watches a PR's checks and send a OS notification once all pass or some are failing
#!/bin/bash
# Script to watch GitHub PR checks and send a notification.
# Usage: ./prwatch.sh [--all] <FULL_PR_URL>
# --all: Wait for all checks to complete before reporting (don't fail eagerly)
# Prerequisites: gh (GitHub CLI) and jq must be installed and configured.
# --- Configuration: Customize your notification command ---
# Uncomment the line for your OS and comment out/delete the others.
# You can also customize the message further if you like.
# macOS:
send_notification() {
# Title, Message
osascript -e "display notification \"$2\" with title \"$1\""
}
# Linux (requires libnotify-bin or similar package for notify-send):
# send_notification() {
# # Title, Message
# notify-send "$1" "$2"
# }
# Windows (using PowerShell - make sure BurntToast module is installed, or use msg.exe):
# This is a bit more involved to call directly from bash, often easier to have a separate .ps1
# or use WSL's interop if you're in that environment.
# For a simpler (but less pretty) built-in Windows notification via WSL or Git Bash:
# send_notification() {
# # Title (ignored by msg), Message
# msg * "$1: $2"
# }
# --- End Configuration ---
if [ -z "$1" ]; then
echo "Usage: $0 [--all] <FULL_PR_URL>"
echo " --all: Wait for all checks to complete before reporting final result"
echo " (instead of failing eagerly when first check fails)"
exit 1
fi
# Parse parameters
WAIT_FOR_ALL=false
PR_URL=""
while [[ $# -gt 0 ]]; do
case $1 in
--all)
WAIT_FOR_ALL=true
shift
;;
-*)
echo "Unknown option $1"
echo "Usage: $0 [--all] <FULL_PR_URL>"
exit 1
;;
*)
if [ -z "$PR_URL" ]; then
PR_URL="$1"
else
echo "Error: Multiple PR URLs provided. Only one is allowed."
echo "Usage: $0 [--all] <FULL_PR_URL>"
exit 1
fi
shift
;;
esac
done
if [ -z "$PR_URL" ]; then
echo "Error: No PR URL provided."
echo "Usage: $0 [--all] <FULL_PR_URL>"
exit 1
fi
# Attempt to get a nice identifier for the PR (e.g., owner/repo#123)
# If this fails, it might be an invalid URL or gh issue.
PR_IDENTIFIER=$(gh pr view "$PR_URL" --json "number,repository" --template '{{.repository.owner.login}}/{{.repository.name}}#{{.number}}' 2>/dev/null)
if [ -z "$PR_IDENTIFIER" ]; then
echo "Error: Could not retrieve PR information. Is the URL valid and gh configured?"
# Fallback to using the raw URL if identifier fetch fails but URL might still work for status
PR_IDENTIFIER_MSG="PR at $PR_URL"
else
PR_IDENTIFIER_MSG="$PR_IDENTIFIER"
fi
echo "Watching PR: $PR_IDENTIFIER_MSG"
if $WAIT_FOR_ALL; then
echo "Mode: Wait for all checks to complete before reporting final result."
else
echo "Mode: Report immediately when any check fails (eager fail)."
fi
echo "Will notify when checks complete (all pass or one fails)."
echo "Prerequisites: gh and jq installed. Notification command configured in script."
echo "---"
while true; do
# Fetch all check states for the PR using 'gh pr checks'.
# This provides a more direct and reliable list of states than statusCheckRollup.
ALL_STATES_RAW=$(gh pr checks "$PR_URL" --json state --jq '.[] | .state' 2>/dev/null)
# Check if the command failed or returned no output (e.g., network issue, invalid PR)
if [ $? -ne 0 ] || [ -z "$ALL_STATES_RAW" ]; then
CURRENT_TIMESTAMP=$(date +"%T")
# Check if it's the initial PR_IDENTIFIER fetch that's failing, or the checks fetch
# The PR_IDENTIFIER_MSG is already set based on an earlier gh call.
# If PR_IDENTIFIER is empty, it means the initial gh pr view for metadata failed.
if [ -z "$PR_IDENTIFIER" ] && [[ "$PR_IDENTIFIER_MSG" == "PR at $PR_URL" ]]; then
echo "[$CURRENT_TIMESTAMP] Error: Could not retrieve initial PR information. Is the URL valid and gh configured? Retrying in 30s..."
else
echo "[$CURRENT_TIMESTAMP] Error: Failed to fetch PR check statuses from GitHub. Retrying in 30s..."
fi
sleep 30
continue
fi
ALL_STATES=$(echo "$ALL_STATES_RAW" | sort | uniq)
# Determine current overall state based on ALL_STATES
# The logic differs based on WAIT_FOR_ALL mode:
# - Eager mode (default): Report failure/error immediately, success when no pending/failing
# - Wait-all mode: Only report final result when no checks are pending/in-progress
if $WAIT_FOR_ALL; then
# Wait-all mode: Only proceed to final result when no checks are pending
if echo "$ALL_STATES" | grep -q "IN_PROGRESS"; then
CURRENT_STATE="PENDING"
elif echo "$ALL_STATES" | grep -q "PENDING"; then
CURRENT_STATE="PENDING"
elif echo "$ALL_STATES" | grep -q "QUEUED"; then
CURRENT_STATE="PENDING"
# All checks have completed, now determine final result
elif echo "$ALL_STATES" | grep -q "FAILURE"; then
CURRENT_STATE="FAILURE"
elif echo "$ALL_STATES" | grep -q "ERROR"; then
CURRENT_STATE="ERROR"
elif echo "$ALL_STATES" | grep -q "SUCCESS"; then
CURRENT_STATE="SUCCESS"
elif echo "$ALL_STATES" | grep -q "SKIPPED"; then
CURRENT_STATE="SUCCESS" # Treat "all skipped" as success
elif [ -z "$ALL_STATES" ]; then
if echo "$ALL_STATES_RAW" | grep -q '[a-zA-Z]'; then
CURRENT_STATE="UNKNOWN"
else
CURRENT_STATE="NO_CHECKS"
fi
else
CURRENT_STATE="UNKNOWN"
fi
else
# Eager mode (original behavior): Report failure/error immediately
# Order of precedence: FAILURE > ERROR > PENDING > SUCCESS > NO_CHECKS / UNKNOWN
if echo "$ALL_STATES" | grep -q "FAILURE"; then
CURRENT_STATE="FAILURE"
elif echo "$ALL_STATES" | grep -q "ERROR"; then # Though 'ERROR' is not a typical state from 'gh pr checks', keep for robustness
CURRENT_STATE="ERROR"
elif echo "$ALL_STATES" | grep -q "IN_PROGRESS"; then # 'gh pr checks' uses IN_PROGRESS
CURRENT_STATE="PENDING"
elif echo "$ALL_STATES" | grep -q "PENDING"; then # Generic PENDING state
CURRENT_STATE="PENDING"
elif echo "$ALL_STATES" | grep -q "QUEUED"; then # Generic QUEUED state
CURRENT_STATE="PENDING"
elif echo "$ALL_STATES" | grep -q "SUCCESS"; then
# If SUCCESS is present, and no failure/error/pending states were found,
# it means all non-skipped checks are successful, or only success/skipped exist.
CURRENT_STATE="SUCCESS"
elif echo "$ALL_STATES" | grep -q "SKIPPED"; then
# If only SKIPPED states are left (or SKIPPED and unknown states not caught above),
# consider it a success as nothing is actively failing or pending.
CURRENT_STATE="SUCCESS" # Treat "all skipped" or "skipped among non-critical" as success
elif [ -z "$ALL_STATES" ]; then
# This means ALL_STATES_RAW was empty after sort|uniq, which implies no checks reported.
# This could also happen if ALL_STATES_RAW contained only newlines or whitespace.
# Let's refine to check if ALL_STATES_RAW effectively yielded no actual states.
if echo "$ALL_STATES_RAW" | grep -q '[a-zA-Z]'; then # Check if there was any actual state text
CURRENT_STATE="UNKNOWN" # Had some text, but didn't match known states after uniq
else
CURRENT_STATE="NO_CHECKS" # Truly no states reported
fi
else
# Contains states not explicitly handled (e.g., new GitHub states)
CURRENT_STATE="UNKNOWN"
fi
fi
CURRENT_TIMESTAMP=$(date +"%T")
case "$CURRENT_STATE" in
"SUCCESS")
echo "[$CURRENT_TIMESTAMP] ✅ All checks passed for $PR_IDENTIFIER_MSG!"
send_notification "✅ PR Checks Passed!" "All checks for $PR_IDENTIFIER_MSG have passed."
break
;;
"FAILURE")
echo "[$CURRENT_TIMESTAMP] ❌ Checks failed for $PR_IDENTIFIER_MSG."
send_notification "❌ PR Checks Failed!" "One or more checks for $PR_IDENTIFIER_MSG failed."
break
;;
"ERROR")
echo "[$CURRENT_TIMESTAMP] ⚠️ Checks reported an error for $PR_IDENTIFIER_MSG."
send_notification "⚠️ PR Checks Errored!" "Checks for $PR_IDENTIFIER_MSG reported an error."
break
;;
"NO_CHECKS")
echo "[$CURRENT_TIMESTAMP] ℹ️ No checks defined for $PR_IDENTIFIER_MSG."
send_notification "ℹ️ No Checks" "No checks were run for $PR_IDENTIFIER_MSG."
break
;;
"PENDING")
echo "[$CURRENT_TIMESTAMP] ⏳ Checks pending for $PR_IDENTIFIER_MSG..."
# No notification, just wait and loop again.
;;
"UNKNOWN")
echo "[$CURRENT_TIMESTAMP] 🤔 Unknown state: '$CURRENT_STATE' for $PR_IDENTIFIER_MSG. Will re-check."
# This case should ideally not be hit if GitHub API is consistent.
;;
*)
echo "[$CURRENT_TIMESTAMP] 🤔 Unknown state: '$CURRENT_STATE' for $PR_IDENTIFIER_MSG. Will re-check."
# This case should ideally not be hit if GitHub API is consistent.
;;
esac
sleep 5 # Wait 5 seconds before checking again
done
echo "---"
echo "Monitoring finished for $PR_IDENTIFIER_MSG."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment