Route CI to your self-hosted runner when it's online, and to a fallback when it
isn't. Fail-open by design: any API error, bad token, or rate limit picks the
fallback with a warning and never blocks CI. Composite action, no Node runtime
(just curl + jq).
jobs:
determine-runner:
runs-on: ubuntu-24.04outputs:
runner: ${{ steps.pick.outputs.runner }}steps:
- uses: devtime-ltd/runner-failover@v1id: pickwith:
runners: | self-hosted ubuntu-24.04token: ${{ secrets.RUNNER_READ_PAT }}build:
needs: determine-runnerruns-on: ${{ fromJson(needs.determine-runner.outputs.runner) }}runners is priority-ordered, one runner label per line. The first line with an
online runner wins; the last line is the unconditional fallback.
| Input | Description |
|---|---|
runners | Priority-ordered candidates (see above). |
token | Fine-grained PAT with Administration: read-only on the repo. GITHUB_TOKEN can't list runners. |
| Output | Description |
|---|---|
runner | JSON label array; use with fromJson() in runs-on. |
used-fallback | "true" when a non-preferred candidate was chosen. |
Create the PAT prefilled, set the resource owner to your org, scope to your repo, and save as a secret. If it expires, CI just uses the fallback until you rotate.
MIT