Self-hosted GitHub Actions runners give teams control over the operating system, installed software, network, hardware, and security configuration used by their workflows.
That control also creates another responsibility: keeping the runner software current.
GitHub self-hosted runners normally update themselves automatically. The problem appears when teams disable automatic updates, use containerized or ephemeral runners, or maintain their own runner images.
In those environments, simply knowing that a newer runner exists is not enough. You need a way to identify the installed version, determine whether that version is approaching end of life, and decide when the runner should be replaced.
The GitHub Actions REST API provides runner-version endpoints that can be used as part of that process.
Why Runner Versions Matter
The runner is the software that receives a GitHub Actions job and executes it on the machine.
A workflow depends on the runner for more than simply starting shell commands. New GitHub Actions functionality can require changes in the runner itself.
This creates a dependency:
GitHub Actions service
|
v
Runner capabilities
|
v
Workflow execution
If the runner is too old, a workflow may not be able to use newer functionality correctly.
GitHub states that when automatic updates are disabled, runners must be updated within 30 days of a new version becoming available. If a required security update is released, the runner may stop receiving jobs until it is updated.
That makes runner version management an operational concern, not just maintenance work.
Automatic Updates vs Managed Updates
Self-hosted runners automatically update by default.
For many environments, that is the simplest option.
The problem is that some environments intentionally disable automatic updates.
For example:
Runner images are built through CI.
Runners run inside containers.
Infrastructure changes require approval.
Production runners follow controlled release windows.
Security teams require software to be validated before deployment.
Organizations use immutable virtual machines.
In these environments, administrators may register a runner with automatic updates disabled:
./config.sh \
--url "$GITHUB_URL" \
--token "$RUNNER_TOKEN" \
--disableupdate
Once automatic updates are disabled, the organization becomes responsible for tracking and deploying runner updates.
What the Runner Version API Provides
GitHub provides REST API endpoints for runner-version lifecycle information.
One particularly useful endpoint returns the end-of-life schedule for a specific runner version.
At the organization level, the response includes information such as:
{
"runner_version": "2.300.0",
"runtime_deprecates_at": "2026-09-01T00:00:00Z"
}
The same type of information is available at repository and enterprise scope.
This makes it possible to build a simple internal process:
Current runner version
|
v
Query lifecycle information
|
v
Check deprecation date
|
+---- Safe -> Continue
|
+---- Approaching -> Plan update
|
+---- Deprecated -> Replace immediately
The API does not automatically upgrade your machines. It gives your automation the information required to make that decision.
Find the Runner Version First
Before building an update process, collect the versions currently deployed.
A runner can expose its version through the installed runner software.
For example, from the runner directory:
./run.sh --version
Your infrastructure inventory should record at least:
Runner name
Runner group
Operating system
Architecture
Runner version
Image version
Last update
Environment
For a larger organization, this can become a small inventory database or configuration-management record.
Example:
runner-prod-01 linux-x64 2.335.0 production
runner-prod-02 linux-x64 2.335.0 production
runner-build-01 linux-arm64 2.334.0 build
runner-win-01 win-x64 2.333.1 testing
Once versions are inventoried, lifecycle checking becomes much easier.
Querying the Version Lifecycle API
The organization-level endpoint follows this pattern:
GET /orgs/{org}/actions/runners/deprecations/{version}
The request needs appropriate authentication and organization runner-management permissions.
A simple C# client can call the endpoint like this:
using System.Net.Http.Headers;
using System.Text.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue(
"application/vnd.github+json"));
client.DefaultRequestHeaders.Add(
"X-GitHub-Api-Version",
"2026-03-10");
var organization = "my-organization";
var runnerVersion = "2.300.0";
var response = await client.GetAsync(
$"https://api.github.com/orgs/{organization}/actions/runners/deprecations/{runnerVersion}");
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
The important design point is that the version is an input.
Your automation can discover the versions currently deployed and query lifecycle information for each one.
Building a Runner Health Check
A useful internal service can turn the API response into a simple status.
For example:
public sealed record RunnerLifecycle(
string RunnerVersion,
DateTimeOffset RuntimeDeprecatesAt);
public static string GetStatus(
RunnerLifecycle lifecycle,
DateTimeOffset now)
{
var remaining =
lifecycle.RuntimeDeprecatesAt - now;
if (remaining <= TimeSpan.Zero)
return "Deprecated";
if (remaining <= TimeSpan.FromDays(14))
return "Update required soon";
if (remaining <= TimeSpan.FromDays(30))
return "Plan update";
return "Current";
}
This is intentionally simple.
The goal is not to create another complicated monitoring system. The goal is to turn runner lifecycle information into an actionable signal.
Enterprise, Organization, and Repository Scope
The API provides lifecycle information at different scopes.
This is useful because runner ownership is not always organized at one level.
For example:
Enterprise
|
+-- Organization A
| |
| +-- Repository 1
| +-- Repository 2
|
+-- Organization B
|
+-- Repository 3
A centrally managed enterprise may want enterprise-level reporting.
An organization administrator may only need organization-level information.
A team managing a repository-specific runner can use the repository-level endpoint.
The important part is choosing the scope that matches ownership.
Use the API for Detection, Not Blind Upgrades
It is tempting to create a script that sees an old version and immediately replaces the runner.
That is not always safe.
A runner update can affect:
Operating system compatibility
Installed tools
Container images
Custom actions
Network configuration
Runner service configuration
Internal security controls
A safer process is:
Detect
|
v
Evaluate
|
v
Test
|
v
Replace
|
v
Verify
This is particularly important for production runners.
Containerized Runners Need a Different Strategy
Container-based runners are a common reason to disable automatic updates.
If the runner software is part of the container image, the preferred approach is usually to update the image rather than letting every running container modify itself.
For example:
FROM ubuntu:24.04
ARG RUNNER_VERSION
RUN curl -L \
"https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz" \
-o runner.tar.gz \
&& tar -xzf runner.tar.gz \
&& rm runner.tar.gz
The exact base image and installation process will depend on the runner architecture and your environment.
The important design principle is to make the runner version an explicit image input.
Then the deployment process becomes:
Runner version approved
|
v
Build new image
|
v
Test image
|
v
Deploy replacement runners
|
v
Remove old runners
This is easier to audit than allowing every ephemeral container to update itself independently.
Self-Hosted Runner Version Inventory
For a larger environment, create a report such as:
Runner | Version | Platform | Lifecycle | Action |
|---|---|---|---|---|
build-01 | 2.335.0 | Linux x64 | Current | None |
build-02 | 2.334.0 | Linux x64 | Current | Monitor |
prod-01 | 2.300.0 | Linux x64 | Deprecated soon | Upgrade |
prod-02 | 2.300.0 | Windows x64 | Deprecated soon | Upgrade |
arm-01 | 2.320.0 | Linux ARM64 | Deprecated | Replace |
The report can be generated daily or as part of the organization's infrastructure pipeline.
This is more useful than discovering an outdated runner only after a workflow stops being queued.
What Happens When a Runner Becomes Unsupported?
When automatic updates are disabled, GitHub does not allow an old runner to remain indefinitely.
GitHub states that if a runner is not updated within the required period, jobs will no longer be queued to it.
A critical security update can cause this to happen sooner.
That means a stale runner can eventually become an availability problem:
Old runner
|
v
Update available
|
v
No update
|
v
Support window expires
|
v
Jobs stop being queued
The best solution is to update before reaching that point.
Common Mistakes
Checking Only the Latest Release
The latest public runner release is not necessarily the version your enterprise receives immediately because runner releases can be rolled out progressively.
Your automation should consider the runner version available to your environment, not blindly assume that the newest release is immediately required everywhere.
Disabling Automatic Updates Without a Replacement Process
--disableupdate transfers the responsibility to your team.
It should never be treated as the end of the update process.
Updating Every Runner at Once
A fleet-wide update can turn a runner problem into a CI availability problem.
Use staged replacement where possible.
Updating Containers In Place
For immutable runner images, rebuild and replace the image rather than modifying running containers manually.
Tracking Only Runner Version
A runner version is important, but the host image, operating system, architecture, installed SDKs, and security configuration also affect workflow reliability.
Best Practices
Keep automatic updates enabled unless you have a specific reason to disable them.
If updates are disabled, maintain an explicit runner-version inventory.
Use the version lifecycle API to identify approaching deprecations.
Build runner updates into your image or infrastructure pipeline.
Test new runner versions before production rollout.
Use staged replacement for important runner pools.
Monitor deprecated and soon-to-be-deprecated versions.
Keep runner architecture information with the version inventory.
Treat security-related runner updates as high priority.
Do not wait for GitHub to stop queuing jobs before updating.
Advantages and Disadvantages
Advantages | Disadvantages |
|---|---|
Gives administrators visibility into runner lifecycle | Requires API authentication and automation |
Helps identify versions approaching deprecation | Does not perform the update itself |
Works with controlled update processes | Adds infrastructure-management work |
Useful for containerized runners | Requires accurate runner inventory |
Supports enterprise-wide governance | Different environments may require different rollout strategies |
A Practical Update Workflow
For an organization managing self-hosted runners, the following process is a good starting point:
1. Inventory all self-hosted runners
|
2. Record installed versions
|
3. Query runner lifecycle information
|
4. Flag versions approaching deprecation
|
5. Select the replacement version
|
6. Build and test the runner image
|
7. Deploy a small canary pool
|
8. Run representative workflows
|
9. Expand the rollout
|
10. Remove obsolete runners
For teams using automatically updated runners, much of this process can be simplified.
For teams using immutable or tightly controlled infrastructure, the process becomes an important part of the platform engineering workflow.
Final Takeaway
Keeping GitHub Actions runners current is easy when automatic updates are enabled. The challenge begins when organizations intentionally manage runner versions themselves.
The runner version lifecycle API provides the information needed to make that process predictable. Instead of waiting for a workflow to fail or a runner to stop accepting jobs, teams can identify aging versions, plan updates, test replacements, and roll them out in stages.
For enterprises with large self-hosted runner fleets, the best approach is to treat runner software like any other production dependency: inventory it, monitor its lifecycle, test updates, and replace unsupported versions before they become a CI availability problem.

Join the conversation! Your thoughts help the community grow.