name: Retry a flaky command description: >- Run a shell command, retrying on non-zero exit. For dependency installs (npm ci, uv sync) whose only failures are transient network/toolchain flakes — a node-gyp header fetch, a registry blip — so CI self-heals instead of needing a manual re-run. inputs: command: description: Shell command to run (and retry). required: true attempts: description: Max attempts before giving up. default: "3" delay: description: Seconds to wait between attempts. default: "10" working-directory: description: Directory to run in. default: "." runs: using: composite steps: - shell: bash working-directory: ${{ inputs.working-directory }} # command goes through env, never interpolated into the script body, so # a command with quotes/specials can't break or inject into the runner. env: _CMD: ${{ inputs.command }} _ATTEMPTS: ${{ inputs.attempts }} _DELAY: ${{ inputs.delay }} run: | set -uo pipefail n=0 while :; do n=$((n + 1)) echo "::group::attempt $n/$_ATTEMPTS: $_CMD" if bash -c "$_CMD"; then echo "::endgroup::" exit 0 fi echo "::endgroup::" if [ "$n" -ge "$_ATTEMPTS" ]; then echo "::error::failed after $n attempts: $_CMD" exit 1 fi echo "::warning::attempt $n failed; retrying in ${_DELAY}s: $_CMD" sleep "$_DELAY" done