Skip to content

Instantly share code, notes, and snippets.

@tommarshall
Created April 2, 2026 07:44
Show Gist options
  • Select an option

  • Save tommarshall/ca48b0b2a5b881cbc9c94d4754ff90bd to your computer and use it in GitHub Desktop.

Select an option

Save tommarshall/ca48b0b2a5b881cbc9c94d4754ff90bd to your computer and use it in GitHub Desktop.
Retry a shell command on failure, with optional exponential backoff. Bash 3 compatible, works on vanilla macOS.
#!/usr/bin/env bash
set -euo pipefail
if [[ -t 2 ]]; then
RED='\033[0;31m'; GREEN='\033[0;32m'; NC='\033[0m'
else
RED=''; GREEN=''; NC=''
fi
# Defaults
MAX_RETRIES=0 # 0 = unlimited
BACKOFF=false
BACKOFF_MAX=30
DELAY=1
usage() {
cat >&2 <<EOF
Usage: retry [OPTIONS] -- COMMAND [ARGS...]
Options:
-n, --retries N Max retries (default: unlimited)
-b, --backoff Enable exponential backoff (default: off)
-d, --delay N Base delay in seconds (default: 1)
-m, --max-delay N Max backoff delay in seconds (default: 30)
-h, --help Show this help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-n|--retries) MAX_RETRIES="$2"; shift 2 ;;
-b|--backoff) BACKOFF=true; shift ;;
-d|--delay) DELAY="$2"; shift 2 ;;
-m|--max-delay) BACKOFF_MAX="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
--) shift; break ;;
*) break ;;
esac
done
[[ $# -eq 0 ]] && { echo -e "Error: no command specified\n" >&2; usage; exit 1; }
attempt=0
while true; do
if "$@"; then
[[ $attempt -gt 0 ]] && { echo >&2; echo -e "${GREEN}✓${NC} Succeeded on attempt $((attempt + 1))" >&2; }
exit 0
fi
exit_code=$?
attempt=$((attempt + 1))
if [[ $MAX_RETRIES -gt 0 && $attempt -ge $MAX_RETRIES ]]; then
echo -e "${RED}✗${NC} Failed after $attempt attempt(s) (exit code $exit_code)" >&2
exit $exit_code
fi
if $BACKOFF; then
wait=$(( 1 << attempt ))
wait=$(( wait > BACKOFF_MAX ? BACKOFF_MAX : wait ))
else
wait=$DELAY
fi
echo >&2
echo -e "${RED}✗${NC} Attempt $attempt failed (exit code $exit_code) — retrying in ${wait}s..." >&2
echo >&2
sleep "$wait"
done
@tommarshall
Copy link
Copy Markdown
Author

Example usage:

# Retry indefinitely with 1s delay between attempts
retry git push

# Limit to 5 attempts
retry --retries 5 git push

# Exponential backoff — delays double each attempt (1s, 2s, 4s...)
retry --backoff git fetch

# 10 attempts with backoff, capped at 60s between retries
retry --retries 10 --backoff --max-delay 60 git fetch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment