← Actions runner starvation check

Why is my GitHub Actions check red in 3 seconds with no output?

You push a commit or open a PR. Every check goes red within a handful of seconds. There is no log to open, no failing assertion, no stack trace — the output body is empty. It happens on a change that could not plausibly break six different workflows at once, including ones that touch files you never edited.

That pattern is not your code. It is the signature of a job GitHub never assigned a runner to: the account's included Actions minutes are spent, a spending limit was hit, there is a payment problem, or Actions has been disabled outright. GitHub's UI does not say any of that on the checks tab — it just shows red, the same red a real failure shows.

Why you can't tell by how fast it failed

A real job can fail in three seconds too — a syntax error, or a test suite's first assertion failing before it does anything expensive. Duration looks identical in both cases, which is exactly why it is the wrong signal to trust, in either direction: it will make you "fix" code that was never broken, and just as easily wave through a real regression because it also failed fast and got blamed on "CI being CI."

The field that actually decides

Every job GitHub Actions reports through its API carries a runner_id. A job that was actually scheduled onto a machine has a real, nonzero id. A job GitHub never got to has runner_id: 0 and an empty runner_name — proof no runner was ever assigned, which means no step in the job could have run, which means the failure cannot be about anything the job would have tested.

gh api repos/OWNER/REPO/actions/runs/RUN_ID/jobs | jq '.jobs[] | {name, runner_id, runner_name}'

If every job in that output shows "runner_id": 0, none of those checks are telling you anything about your change. If they show a real id, the failure is real and worth debugging normally.

Paste that same JSON into the Actions Runner Starvation Check page for a plain verdict instead of reading the fields by eye, or run the zero-dependency check.py CLI in a script so it can decide for you: python3 check.py --run RUN_ID exits 1 the moment any job was starved.

What this does not tell you

Only that a runner was or was not assigned — not why your account stopped getting them. GitHub's own run-timing endpoint reports zero billable milliseconds for private repositories regardless of what actually ran, so it is no help attributing the cause either. Exhausted minutes, a spending limit, a payment problem, and Actions being disabled all look identical from the Actions API; that distinction is on your GitHub billing page, not something any tool reading the jobs API can recover.