GitHub's New Runner Deprecation API: Automate Your Self-Hosted Fleet Audit
GitHub's New Runner Deprecation API: Automate Your Self-Hosted Fleet Audit
If you manage self-hosted GitHub Actions runners, you've probably already been checking versions by hand โ SSH into a box, run ./run.sh --version, repeat for every machine. On September 3, 2026, GitHub shipped a REST API that makes that manual sweep unnecessary: GET /actions/runners/deprecations/{version} returns the exact date a given runner version stops registering and the exact date it stops picking up jobs, for any version you ask about, at the repo, org, or enterprise level.
This lands at a genuinely useful moment. GitHub's minimum-version enforcement for self-hosted runners on github.com and GitHub Enterprise Cloud reaches full, permanent enforcement on September 25, 2026 โ tomorrow, as of this writing. If your fleet still has stragglers below v2.329.0, or runners that haven't pulled a release in the last 30 days, this new endpoint is the fastest way to find them before they go dark.
What the endpoint actually returns
1GET /actions/runners/deprecations/{version}
2GET /orgs/{org}/actions/runners/deprecations/{version}
3GET /enterprises/{enterprise}/actions/runners/deprecations/{version}
4GET /repos/{owner}/{repo}/actions/runners/deprecations/{version}
Query it with a specific runner version string, and the response looks like this:
1{
2 "runner_version": "2.300.0",
3 "runtime_deprecates_at": "2026-09-01T00:00:00Z",
4 "registration_deprecates_at": "2026-07-01T00:00:00Z"
5}
Two separate timestamps matter here, and they're not the same thing:
registration_deprecates_atโ after this date, a runner running this version can no longer register or re-register with GitHub at all.runtime_deprecates_atโ after this date, an already-registered runner on this version stops being able to pick up jobs, even if it registered successfully before the cutoff.
Registration deprecation almost always lands before runtime deprecation for a given version โ GitHub gives you a window where an old runner still executes jobs but can't be freshly registered, which matches the phased brownout pattern already rolling out this month.
Why this beats checking versions one machine at a time
The old workflow โ the one described in most "how to fix your runner version" guides right now โ is reactive: you find out a runner is too old when a job fails during a brownout window, or when someone remembers to SSH in and check. This API flips that into something you can script and run on a schedule, catching stragglers before enforcement bites instead of after.
The other piece you need alongside it is the existing self-hosted runners list endpoint, which tells you what versions are actually deployed across your fleet right now:
1gh api /orgs/YOUR_ORG/actions/runners --paginate \
2 --jq '.runners[] | {name, id, status, labels: [.labels[].name]}'
That endpoint doesn't return the runner's version string directly in every API version, so the more reliable source of the installed version is your own inventory (config management, Terraform state, or the audit log's registration events) cross-referenced against what the deprecation endpoint tells you about each version's fate.
Step-by-step: scripting a fleet audit
1. Get the list of distinct runner versions currently deployed. If you provision runners from Terraform, Packer, or Ansible, the pinned version string is usually already in your IaC repo โ that's your source of truth, not a live SSH sweep:
1grep -rhoE 'runner[_-]version["\s:=]+[\"'\''"]?v?[0-9]+\.[0-9]+\.[0-9]+' \
2 terraform/ ansible/ packer/ 2>/dev/null | sort -u
2. Query the deprecation endpoint for each version you found. Using gh (the GitHub CLI, which handles auth for you):
1for v in 2.319.0 2.322.0 2.328.1; do
2 echo "== $v =="
3 gh api "/orgs/YOUR_ORG/actions/runners/deprecations/$v" \
4 --jq '{runner_version, registration_deprecates_at, runtime_deprecates_at}'
5done
3. Flag anything with a deprecation date in the past or within your alert window. Wrap the loop above in a date comparison so it exits non-zero (and can fail a CI job or trigger a Slack alert) when a version is already past registration_deprecates_at or within, say, 14 days of runtime_deprecates_at:
1now=$(date -u +%s)
2warn_threshold=$(( now + 14*86400 ))
3
4for v in 2.319.0 2.322.0 2.328.1; do
5 resp=$(gh api "/orgs/YOUR_ORG/actions/runners/deprecations/$v")
6 rt=$(echo "$resp" | jq -r '.runtime_deprecates_at')
7 rt_epoch=$(date -u -d "$rt" +%s 2>/dev/null || date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "$rt" +%s)
8 if [ "$rt_epoch" -lt "$warn_threshold" ]; then
9 echo "WARNING: runner $v hits runtime deprecation at $rt"
10 fi
11done
4. Run this on a schedule, not just once. A GitHub Actions workflow on a weekly schedule trigger, calling this script against your org and posting failures to Slack or opening an issue, turns a one-time cleanup into a standing guardrail. That's the actual value of this API over the manual ./run.sh --version approach โ it's the difference between a task and a control.
Best practices
- Query by org or enterprise, not just per-repo, if you manage runners centrally โ it's one API call surface instead of one per repository.
- Don't hardcode version strings in your audit script for long. Pull them from your IaC or provisioning source so the audit always reflects what's actually deployed, not what was deployed when you wrote the script.
- Pair this with auto-update. The deprecation API tells you what's at risk; it doesn't fix anything. If your runners have auto-update disabled (common when pinning for reproducibility), this audit just becomes a recurring warning with no resolution path until someone re-provisions.
- Alert on
registration_deprecates_atearlier thanruntime_deprecates_at. Losing the ability to register new runners is often the first sign of trouble for autoscaling or ephemeral runner setups (likeactions-runner-controlleron Kubernetes), even before any currently-running job is affected.
Common mistakes to avoid
- Confusing the two timestamp fields. A runner past
registration_deprecates_atbut not yet pastruntime_deprecates_atis still executing jobs fine โ don't page anyone for that alone, but do treat it as a "fix this before you need to scale up" signal. - Checking only the versions you remember provisioning. Runners drift โ a base image built eight months ago and never rebuilt is a very common source of a version nobody remembers pinning.
- Treating this API as a substitute for actually upgrading. It's a detection tool. The fix is still rebuilding the image, re-running
config.shagainst a newer binary, or re-enabling auto-update. - Ignoring ephemeral/ARC runners because "they're always fresh." They're only as fresh as the container image they're built from โ if that image itself is stale, every ephemeral runner it spins up inherits the same deprecated version.
Troubleshooting
If gh api /actions/runners/deprecations/{version} returns a 404, double-check the version string format โ it needs to match a real published runner release exactly (e.g. 2.328.0, not v2.328.0 with the leading v, and not a partial version). If you get a 403, confirm your token has the actions:read scope at the level you're querying (repo, org, or enterprise) โ this is a read endpoint but still scope-gated like the rest of the Actions API surface.
FAQ
Does this API tell me what version my runners are currently running? No โ it tells you the fate of a version you already know about. You still need your own inventory (IaC state, audit logs, or a live sweep) to know which versions are actually deployed; this endpoint is the second half of the picture, not the first.
Is this the same as the self-hosted runners list endpoint? No. The runners list endpoint (GET /orgs/{org}/actions/runners) tells you what runners are registered and their status/labels. The new deprecation endpoint tells you the lifecycle dates for a specific version string. Use both together.
Does this affect GitHub-hosted runners? No โ this is specific to self-hosted runner version lifecycle, same scope as the minimum-version enforcement rolling out this month.
Can I query a future or hypothetical version to plan ahead? The endpoint is documented against published runner versions; querying a version that hasn't been released yet will return a 404 rather than a projected date, since GitHub sets deprecation dates when each version ships, not in advance.
Does this replace watching the audit log for registration events? Not entirely โ the audit log tells you what's registering now; the deprecation API tells you what happens to a given version in the future. A mature setup uses both: audit logs for real-time visibility, the deprecation API for proactive planning.
Key takeaways
| Fact | Detail |
|---|---|
| Endpoint | GET /actions/runners/deprecations/{version} (repo, org, or enterprise scope) |
| Shipped | September 3, 2026 |
| Response fields | runner_version, runtime_deprecates_at, registration_deprecates_at |
| Registration vs. runtime | Registration cutoff usually lands first; runtime cutoff is when jobs stop running |
| Best use | Script a scheduled fleet audit, not a one-time manual check |
| Related deadline | Full self-hosted runner minimum-version enforcement lands September 25, 2026 |
Further Reading
- GitHub Actions Self-Hosted Runners: Fix Your Version Before the September 25 Enforcement
- GitHub Actions' New vulnerability-alerts Permission: Read Dependabot Alerts Without a PAT
- Block GitHub Pull Requests With Exposed Secrets: New Ruleset Rule Setup Guide
- GitHub Changelog: GitHub Actions: Early September 2026 updates
- GitHub Docs: REST API endpoints for self-hosted runners