Skip to content
shellhacks.dev
CI/CD

Use a retry wrapper for flaky steps

Retry a flaky command in a CI step with a small Bash function that doubles the wait between attempts and returns the last failure so the step still fails.

2 min read
Summarize in AI

A download or a deploy call fails now and then for reasons outside your control, and rerunning the whole job takes ten minutes.

Shell
retry() {
  local max=$1 delay=$2 n=1; shift 2
  until "$@"; do
    if (( n >= max )); then echo "giving up after $n attempts" >&2; return 1; fi
    echo "attempt $n failed, retrying in ${delay}s"
    sleep "$delay"; delay=$(( delay * 2 )); n=$(( n + 1 ))
  done
}
echo 0 > count
flaky() { n=$(( $(cat count) + 1 )); echo $n > count; [ "$n" -ge 3 ]; }
retry 5 1 flaky && echo "succeeded after $(cat count) calls"
echo ---
retry 2 1 false; echo "status: $?"
Text
attempt 1 failed, retrying in 1s
attempt 2 failed, retrying in 2s
succeeded after 3 calls
---
attempt 1 failed, retrying in 1s
giving up after 2 attempts
status: 1

until "$@" runs the command and repeats while it fails, so the function retries any command with its arguments. The counter stops it after max attempts, and the wait doubles each time to give a struggling service room. When it gives up it returns 1, which is what makes the CI step fail honestly instead of passing after a hidden error. The loop condition is described in the Bash manual.

Retry only operations that are safe to repeat, such as a download or a read. A deploy or a payment that failed after doing its work could run twice. Add a random jitter to the delay when many jobs hit the same service at once. GitHub Actions has no built-in retry for a run step, so the loop belongs in the script.

Share

More snippets

CI/CD

Run a script from the repo

Run a repository script from a workflow with run: ./script.sh only if Git stores it as executable. Otherwise the step fails with Permission denied, status 126.

2 min read
CI/CD

Group log lines in GitHub Actions

Fold log output in GitHub Actions by printing ::group::Title and ::endgroup::. A wrapper function keeps the pair together and passes the command's status on.

1 min read
CI/CD

Print a warning annotation

Print ::warning file=...,line=...::message from a step to show an annotation on the run and in the pull request diff. Newlines in the text must be encoded.

1 min read

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.