MOTIR-3149Done
decision: does push-to-`main` get its own lane — Motir's fleet and/or a deploy-only main run — or did the ceiling lift and the fan-out cut already buy it?
GitHub has no overflow scheduling and no job priority: runs-on binds when the job is created, a label list is AND (not a fallback chain), and runner groups are access control. So a main run cannot be made to jump a saturated hosted pool — it can only be routed elsewhere or made smaller. Decide which, after the two sibling cards land and the queue is re-measured.
The options, and what each costs
- (a) Nothing further. The ceiling lift + fan-out cut may already leave
mainun-starved. This is the null hypothesis and it must be tested first. - (b) Route push-to-
mainonto Motir's own fleet —runs-on: ${{ github.event_name == 'push' && vars.MOTIR_RUNNER || 'ubuntu-latest' }}. The fleet is demand-provisioned offworkflow_job.queued(ADR §6, p50 ≤ 30 s), so it has no busy state and needs no overflow semantics. - (c) Make
main's run deploy-only — the PR already validated the identical tree onrefs/pull/N/merge, so re-running 37 jobs on the merge commit is near-duplicate work and the thing being starved. Combined with (b),maincosts ~2–3 fleet jobs. - (d) Merge queue — verify before the merge; the post-merge run becomes release, not gate.
What the decision must resolve, or it is not a decision
- Does §J still hold?
docs/decisions/ci-minutes-allowance.md§J records that "motir-coreitself stays GitHub-hosted". (b) contradicts it. Either amend §J with the reason, or reject (b) — do not leave the ADR and the workflow disagreeing. - The runner-group shape. §7.3 mandates one access-listed group per Motir project and forbids an org-wide group;
motir-coreis not a Motir-provisioned project. Name which group it joins, or record why it is the exception. - The admission gate becomes the new cap. §6 states plainly that the binding constraint is Motir's own gate (MOTIR-1922), not GitHub. A run routed in without a sized per-project cap gets deferred — the same starvation with a different owner.
- Secrets. The fleet is built for agent-authored customer code: run-scoped auth, destroyed after one job, no repo secrets by design (§7.2/§7.4).
deployneeds Fly credentials. Say how, or excludedeployfrom (b). - Cost, against a measured basis. Metered 7 days to 2026-08-19: ~200,000 job-minutes/month over ~45,700 jobs. At §8's $0.00195/min that is ~$450/month all-in with boot/teardown — but §8 fixes the spec at private-repo parity (2 vCPU / 8 GB) because "every Motir-created repository is private", and
motir-coreis public, whose hosted runner is larger. Matching it is ~$900/month. Whichever number the decision uses, it replaces $0 — public-repo hosted minutes are free. Re-measure; do not inherit these figures. - The spend ceiling. §9 records that Fly offers neither a cap nor an alert. Say what bounds the bill, given the wedged-leg class (MOTIR-2970, MOTIR-3128) bills to the 45-minute timeout.
Acceptance criteria
- The queue is re-measured after both sibling cards are done, and the numbers are recorded on this card. Option (a) is chosen or rejected on that measurement, not on the pre-change figures above.
the created→first-job-started delay for at least 5 push-to-→ AMENDED 2026-08-19. The sample is 3, and the shortfall is structural rather than impatience: most push-to-mainrunsmainruns never start a job at all. MOTIR-3106 deliberately holds one pending run per concurrency group, so a merge landing during another merge's run cancels the older pending one with zero jobs — 10 of the 20 most recentmainruns. "Lag to first job" is undefined for those, so this sample can never be filled on demand. Replaced by queue depth (jobs_queued), sampled repeatedly, which answers the question more directly: nothing waiting for a runner means no run can be starved of one — a statement about the mechanism rather than a sample of the symptom.
- Every one of the six questions above has a written answer in the outcome, including the ones that argue against the chosen option.
- If an option other than (a) is chosen,
docs/decisions/ci-runner-fleet.md§J is amended in the same PR — a decision that contradicts a live ADR and leaves it standing is not landed. (§J is inci-minutes-allowance.md, notci-runner-fleet.md— corrected 2026-08-19;ci-runner-fleet.md§8 cites it.) - The outcome names the follow-on cards to build it, or states in one line that none are owed.
Out of scope
Implementing (b), (c) or (d). This card decides; the build is whatever it files.