Skip to content

ADR-0013 Use GitHub-Hosted Runners for Public CI

Context and scope

Needlr is a public GitHub repository. Its workflows nevertheless carried two self-hosted execution paths: a general-purpose PitCrew fallback and a specialized needlr-ci profile backed by a repository-owned GHCR image. The repository also used CI_RUNNER to select those paths dynamically.

GitHub's billing documentation states that standard GitHub-hosted runners are free for public repositories. Larger GitHub-hosted runners remain billable. Actions artifact storage is metered separately, while public GitHub Packages and Container registry usage are free under the current policy.

GitHub's standard public-runner table specifically lists windows-latest as a standard Windows runner and states that standard hosted runner use is free and unlimited on public repositories. Needlr requires that runner for its Visual Studio VSIX builds and its Windows-targeted .NET MAUI example.

The self-hosted paths reduced repeated SDK and Native AOT setup, but they also required image publication, digest activation, external capacity management, routing variables, fork-specific trust logic, and repository validation dedicated to that infrastructure. Those responsibilities no longer provide proportional value when standard hosted capacity is available without runner-minute charges.

This decision governs Needlr's GitHub Actions runner selection, delivery manifest, toolchain setup boundary, and short-lived workflow artifacts. It does not change the repository's required checks, draft-versus-ready validation policy, release staging, GitHub Pages publication, or package destinations.

Decision drivers

  • Public-repository CI should use the standard hosted capacity GitHub provides without runner-minute charges.
  • Pull requests from forks and repository branches should share one managed execution boundary.
  • Runner selection must be explicit and reviewable rather than controlled by a mutable repository variable.
  • The exact .NET SDK and Native AOT prerequisites must remain reproducible.
  • Larger billed runners must not enter the workflow accidentally.
  • Actions artifact retention should remain bounded independently of free runner minutes.
  • Removing self-hosted execution must also remove its image, profile, validation, and operational guidance.

Decision

Needlr will run every GitHub Actions job on a standard GitHub-hosted runner declared directly in the workflow.

Linux build, test, packaging, Native AOT, mutation, benchmark, documentation, and release jobs use ubuntu-24.04. Lightweight coordination jobs may use ubuntu-latest. Windows-specific IDE and MAUI jobs use windows-latest.

The delivery contract declares github-hosted as the runner provider and allowlists the exact labels used by the repository. A repository test rejects dynamic runs-on expressions, self-hosted labels, undeclared runner labels, and the former runner image and profile surfaces.

CI_RUNNER, the PitCrew profile, repository-owned runner Dockerfile, image publication workflow, and their dedicated validation scripts are removed. Needlr will not retain a dormant self-hosted fallback.

global.json remains the exact SDK source of truth. The shared setup action installs that SDK when the selected hosted image does not already contain it. Native AOT jobs install clang, file, and zlib1g-dev before publication.

Explicit Actions artifacts use one-day retention. This bounds storage independently of runner-minute pricing while preserving same-run transfer between staged jobs.

Alternatives considered

Keep the specialized PitCrew image

This avoids repeated SDK and Native AOT setup and provides controlled worker images. It was rejected because it retains an external runner lifecycle, image publication, digest rollout, and trust boundary solely to optimize setup that standard hosted workers can perform within the existing workflow.

Keep CI_RUNNER with a hosted default

This would preserve an easy route back to self-hosted capacity. It was rejected because the mutable variable would keep runner selection outside reviewed workflow code and would preserve dormant infrastructure and validation with no current requirement.

Use GitHub-hosted larger runners

Larger runners could reduce long build or mutation durations. They were rejected because GitHub bills them even for public repositories, and current jobs already have bounded parallelism and timeouts on standard runners.

Consequences

Positive

  • Needlr no longer owns runner registration, capacity, image publication, or rollout.
  • Fork and internal pull requests use the same GitHub-managed execution boundary.
  • Runner labels are visible in reviewed workflow files and checked against a committed allowlist.
  • Public-repository standard runner minutes do not consume the private-repository minute allowance.
  • Removing the GHCR runner image stops future image versions and publication artifacts.

Negative

  • Hosted jobs may spend additional time installing the exact SDK and Native AOT prerequisites.
  • GitHub-hosted runner availability and image changes are external dependencies.
  • A future need for specialized hardware or images requires a new decision rather than changing a repository variable.

Neutral

  • Required checks, draft validation, mutation bounds, release staging, and deployment permissions remain unchanged.
  • Actions artifacts and package visibility remain separate billing concerns from standard runner minutes.
  • Historical ADR and changelog entries continue to describe the superseded runner-image architecture.

Confirmation

scripts/test-hosted-runner-policy.ps1 validates the hosted provider declaration, allowed labels, direct runs-on values, and absence of the removed self-hosted surfaces. CI runs that test before build and package validation.

The pull request implementing this decision must complete its full checks on the declared GitHub-hosted runners. Repository settings no longer need CI_RUNNER after the hosted-only workflows reach the default branch.

References