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:

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:

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

  1. Keep automatic updates enabled unless you have a specific reason to disable them.

  2. If updates are disabled, maintain an explicit runner-version inventory.

  3. Use the version lifecycle API to identify approaching deprecations.

  4. Build runner updates into your image or infrastructure pipeline.

  5. Test new runner versions before production rollout.

  6. Use staged replacement for important runner pools.

  7. Monitor deprecated and soon-to-be-deprecated versions.

  8. Keep runner architecture information with the version inventory.

  9. Treat security-related runner updates as high priority.

  10. 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.