Rendered from docs/process/decisions/0016-ci-runner-is-an-opt-in-that-only-a-push-or-a-merge-group-reads-and-an-unset-value-falls-back-to-ubuntu-latest.md in the Headwater
corpus. Every document on this half of the site is typed by the taxonomy
the descriptor names: corpus.json.
CI_RUNNER is an opt-in that only a push or a merge group reads, and an unset value falls back to ubuntu-latest
Context
Before this record, the reasons lived in comments of .github/workflows/ci.yml on 2026-09-27. The comments were on the runs-on of the engine job (lines 213 to 222) and at the end of the header (lines 73 to 77). The runs-on expressions are at lines 223 and 493. Two triggers, one verdict holds the argument.
The variable lives in the repository settings and not in the tree. So a reader of the tree cannot see its value, and a document that states the value goes stale when a person changes it.
Decision
An opt-in. runs-on reads vars.CI_RUNNER only when the event is a push and the router did not report overflow (HW-PD-0018). Since HW-PD-0020, a merge_group run reads it in the same way as a push. When the variable is unset, the expression falls back to ["ubuntu-latest"]. The self-hosted labels reach a job only when the variable names them, with gh variable set CI_RUNNER --body '["self-hosted", "headwater"]'.
A pull request never reads it. A pull_request run takes the last branch of the expression, ubuntu-latest, whatever the variable holds.
Read the live value. Run gh variable list before you state which runner a push uses.
Consequences
gh variable delete CI_RUNNER sends every run to a hosted runner, with no commit. A statement that the self-hosted labels are the default is wrong.
A pull request that is CONFLICTING against a moved main gets no check suite on either runner. From outside, that looks the same as a dead runner. So read mergeable before you read a missing check suite as a runner fault.