1. Get the run's job JSON
Run this for the failing run (needs the gh CLI, already logged in):
gh api repos/OWNER/REPO/actions/runs/RUN_ID/jobs
No gh CLI? Open https://api.github.com/repos/OWNER/REPO/actions/runs/RUN_ID/jobs in a browser tab authenticated with a token that has Actions: read, and copy the response body.
2. Paste it here
Paste the JSON from step 1 and click Check.
Verdict
Prefer a CLI, or want this in a workflow step?
Same logic, zero dependencies, one file:
python3 check.py --run RUN_ID # exits 1 if any job was starved
python3 check.py --run RUN_ID --json # machine-readable
python3 check.py --from-json jobs.json # classify a payload you already have, no API call
Get check.py from the repo — stdlib only, nothing to install. It talks to the API itself via the gh CLI or a GH_TOKEN/GITHUB_TOKEN it finds in your environment, which is the route a hosted CI step or an agent session actually has.
Common questions
Why did my check fail in 3 seconds with no output?
That is the signature of a job GitHub never assigned a runner to — included minutes spent, a spending limit, a payment problem, or Actions disabled on the account. Every check fails within a few seconds with an empty body, and it looks identical to a real fast failure from the checks list alone.
Why not just go by how fast it failed?
Because a real job can fail in three seconds too — a syntax error or a failing assertion on the first test does not need a runner-minute to fail. Duration looks the same either way; only runner_id tells them apart, which is the one field this tool decides on.
Does this tell me why my account stopped getting runners?
No — only whether a runner was assigned. GitHub's own run-timing endpoint reports zero billable time for private repos regardless of what ran, so it cannot attribute cost either. That answer is on your GitHub billing page; this tool only tells you whether the next few minutes are worth spending on your code or on your Actions usage.
Why do I have to paste JSON instead of it just checking for me?
Checking for you would mean either a server that sees every token pasted into it, or this page fetching on your behalf with a token it would have to hold. Neither is worth the convenience next to pasting the response you already fetched yourself with your own credentials.
More: why a GitHub Actions check goes red in seconds with no output, add a starvation guard step to your own workflow so a starved run reports itself, or this checker vs. just re-running the job.
Related: if the job did get a runner and tests flip between runs, the flaky test finder classifies which tests are flaky versus broken from JUnit XML you paste — nothing uploaded.
Related: if the job did get a runner and line coverage fell on the PR, the coverage drop check compares two Cobertura or JaCoCo reports and names the files that got worse — nothing uploaded.