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
