GitHub Actions self-hosted runners need more than a one-time upgrade to version 2.329.0. GitHub Enterprise Cloud begins full minimum-version enforcement on September 25, 2026, and the runtime requirement continues to move as new runner releases appear. Audit the version reported by every runner, query GitHub’s version-specific deprecation endpoint, and update the image or installation source that creates the runner—not only the currently registered machine.

Understand the two version gates

GitHub now describes two separate compatibility rules. Version 2.329.0 is the floor for configuring or re-registering a runner on the rebuilt service. It is not a permanent runtime target. A runner must also receive every major, minor, or patch runner release within 30 days of that release becoming available. A critical security release can stop job assignment immediately until the update is installed.

For GitHub Enterprise Cloud, the final brownouts run on September 14, 16, and 18 from 11:00 AM to 3:00 PM ET. Full enforcement starts September 25. The change applies to github.com, including Enterprise Cloud and Enterprise Cloud with Data Residency; GitHub Enterprise Server is not included in this enforcement notice.

That distinction changes the audit question. Do not ask only whether a runner is at least 2.329.0. Ask whether its exact version can still register and still execute jobs on the date when you need it.

Audit GitHub Actions self-hosted runners

Start with the organization endpoint because it returns the currently configured runners and now includes a version property. The endpoint requires organization administration access, or a fine-grained token with read access to organization self-hosted runners.

#!/usr/bin/env bash
set -euo pipefail

: "${ORG:?Set ORG to your GitHub organization}"

gh api --paginate \
  -H "X-GitHub-Api-Version: 2026-03-10" \
  "/orgs/${ORG}/actions/runners?per_page=100" \
  --jq '.runners[] | [
    .id,
    .name,
    .os,
    .status,
    (.busy | tostring),
    (.ephemeral | tostring),
    (.version // "UNKNOWN"),
    ([.labels[].name] | join(","))
  ] | @tsv' \
  | sort -t $'\t' -k7,7V \
  > runner-inventory.tsv

The script keeps the raw operational fields instead of collapsing the result to a version count. Runner name, online state, ephemeral flag, and labels help map a stale registration back to its owner and provisioning path. Treat UNKNOWN as an audit failure: it means the inventory cannot prove the runtime version, not that the runner is current.

Repository-scoped runners require the corresponding repository endpoint. Enterprise-scoped fleets should also be inventoried at the scope where they are registered. Do not assume the organization response includes runners owned by a different scope.

Query the deadline for every observed version

GitHub exposes a version-specific endpoint that returns when registration and runtime support end. Query it for every distinct version observed in the inventory instead of maintaining a hard-coded comparison in your own script.

cut -f7 runner-inventory.tsv \
  | grep -v '^UNKNOWN$' \
  | sort -Vu \
  | while IFS= read -r version; do
      gh api \
        -H "X-GitHub-Api-Version: 2026-03-10" \
        "/orgs/${ORG}/actions/runners/deprecations/${version}" \
        --jq '[
          .runner_version,
          (.registration_deprecates_at // ""),
          (.runtime_deprecates_at // "")
        ] | @tsv'
    done \
  > runner-deprecations.tsv

Keep the full JSON response in an audit artifact if your compliance process needs exact field provenance. The tab-separated view is useful for triage, but the API response remains the source of truth. A successful lookup does not make a version safe indefinitely; repeat the check as part of the image-release pipeline because the runtime deadline advances with releases.

Find the source that will recreate the stale version

Updating one running process is not enough when automation can recreate the old binary tomorrow. For each version in the inventory, identify the durable source:

  • a pinned download URL or checksum in a VM-image build;
  • an actions/runner package version in a container image;
  • an autoscaling launch template or golden image;
  • the runner image used by Actions Runner Controller;
  • an installation script that caches an older archive;
  • a long-lived installation with automatic updates disabled.

Search infrastructure repositories for the observed version and for --disableupdate:

rg -n --hidden \
  --glob '!**/.git/**' \
  --glob '!runner-inventory.tsv' \
  '(actions-runner|RUNNER_VERSION|--disableupdate|2\.329\.0)' .

The literal 2.329.0 search is diagnostic, not a recommended pin. It finds teams that treated the registration floor as a permanent target. Also inspect artifact caches, image registries, and deployment variables because the effective version may not be committed as plain text.

This runner audit is separate from the JavaScript runtime bundled inside actions. If the same workflow inventory still references Node.js 20 actions, use the GitHub Actions Node.js 20 audit to fix that dependency graph without conflating it with the runner-agent version.

Choose the correct update model

Long-lived runners normally auto-update. That is the lowest-maintenance option only when the runner can reach GitHub’s update service, has write access to its installation directory, and remains online long enough to finish the update. Validate those assumptions rather than inferring success from the default setting.

Immutable or ephemeral runners are different. GitHub documents --disableupdate for images that should receive the runner binary during image construction. In that model, the image pipeline owns the 30-day requirement. A rebuild that changes only the controller or orchestration chart but leaves the runner image pinned does not update the process that executes jobs.

Use a rolling replacement:

  1. Build a new runner image from the version GitHub currently offers for your scope.
  2. Verify its published checksum before adding it to the image.
  3. Start a small canary pool with the same labels and network policy as production.
  4. Send representative workflows to the canary pool.
  5. Drain old runners so they receive no new jobs, but allow busy jobs to finish.
  6. Replace the remaining instances and confirm the registered version through the API.

Do not delete a busy runner merely to make the inventory green. That can terminate an in-flight deployment. The control plane should stop new assignment first, observe busy=false, then remove or recycle the instance.

Add a fail-closed CI check for the fleet

The audit should become a scheduled control, not a spreadsheet that expires. The following check fails when GitHub reports a runtime deadline inside the chosen safety window.

#!/usr/bin/env bash
set -euo pipefail

: "${ORG:?Set ORG}"
safety_days="${SAFETY_DAYS:-7}"
now_epoch="$(date -u +%s)"
limit_epoch="$((now_epoch + safety_days * 86400))"
failed=0

while IFS=$'\t' read -r version _registration_end runtime_end; do
  if [[ -z "$runtime_end" ]]; then
    printf 'UNVERIFIED\t%s\tmissing runtime deadline\n' "$version" >&2
    failed=1
    continue
  fi

  end_epoch="$(date -u -d "$runtime_end" +%s)"
  if (( end_epoch <= limit_epoch )); then
    printf 'ACTION_REQUIRED\t%s\t%s\n' "$version" "$runtime_end" >&2
    failed=1
  fi
done < runner-deprecations.tsv

exit "$failed"

Run this on GitHub-hosted infrastructure or a separately maintained runner so an already blocked self-hosted fleet cannot suppress its own warning. GNU date -d is not portable to macOS; use a small language runtime for date parsing or install GNU coreutils if the audit job runs there. Keep the token read-only and do not print headers or authentication diagnostics into logs.

Verify the upgrade at three layers

A successful image build proves only that an artifact exists. Verify the complete handoff:

  1. Control-plane readback: Re-run the runner inventory and confirm every expected registration reports the intended version. Investigate unexpected offline registrations rather than silently filtering them out.
  2. Execution readback: Route a canary workflow to each important label combination and print only the non-sensitive runner identity and architecture. Confirm jobs start during a brownout window if one remains.
  3. Provisioning readback: Create a new instance from each production image or launch template. Confirm the new registration reports the same version as the canary; this catches stale autoscaling sources.

Keep a rollback image, but do not roll back to a version whose runtime deadline has passed. Rollback planning must remain inside the supported window. For stateful build workloads, preserve external caches and artifacts independently from the runner so replacement does not depend on a mutable runner disk.

Handle edge cases before enforcement

Disconnected networks are the most common hidden risk. Auto-update cannot help a runner that cannot reach the update service, and an image pipeline cannot help if an autoscaling group still points at an older image identifier. Test outbound access from the actual runner subnet and compare the deployed image digest or VM image version with the approved release artifact.

Large fleets can contain registrations that rarely run. GitHub notes that audit-log registration events are useful but are not a complete inventory, because they describe registration-time activity. Combine audit-log evidence with the runner-list API and the infrastructure inventory. An offline runner may be an abandoned record, a powered-down disaster-recovery runner, or a production capacity reserve; each needs an owner and a disposition.

Finally, separate GitHub Enterprise Server from github.com in reports. The September 25 enforcement notice does not apply to GHES, but a mixed fleet can make a broad “all runners are compliant” statement misleading. Record the platform and registration scope with every version result.

References

Found this useful? Support more practical developer content.

Author

Practical .NET, Angular, Azure, Blazor, and AI engineering for real-world development.

Write A Comment

Ads Blocker Image Powered by Code Help Pro

Ads Blocker Detected!!!

We have detected that you are using extensions to block ads. Please support us by disabling these ads blocker.

Powered By
100% Free SEO Tools - Tool Kits PRO