Skip to content

Billing-aware runner routing: prefer-hosted-while-free policy for private repos (gated on App billing probe) #252

Description

@kyle-sexton

Goal

Public repos: always hosted (free). Private repos: consume included hosted minutes FIRST while pool headroom exists and no budget block; otherwise — and on ANY error — route to the self-hosted fleet.

This was believed "not possible." Research (2026-07-26, adversarially re-graded by an independent verifier) refutes that: GitHub ships no native fallback mechanism (confirmed absent — runs-on arrays AND-match; GitHub explicitly declined first-party fallback in community discussion #20019, and the 2026 changelog through Jun was fetched directly to confirm nothing shipped), but this org already owns the substitute seam: select-runner's dynamic runs-on.

Research basis (findings inlined; artifact is session-local memory tier, not linkable)

  • Usage API: GET /organizations/{org}/settings/billing/usage (+ /usage/summary) — live-verified HTTP 200 on this org; the legacy /orgs/.../settings/billing/actions endpoint returns 410 and is removed from the OpenAPI spec. Classic PAT admin:org verified live; GitHub App tokens expected to work (OpenAPI enabledForGitHubApps: true) but unverified pending Phase 0; fine-grained PATs officially unsupported; a workflow-scoped GITHUB_TOKEN can NEVER read billing (no such permission exists).
  • Budgets API: readable (this org's $0 Actions budget with prevent_further_usage: true is visible) but exposes CONFIG ONLY — no consumed-amount field. Spend must be derived from usage rows.
  • Freshness: no official SLA; empirically same-day in one probe — treat as hours-behind, never real-time. Hence headroom margins, not exact cutoffs.
  • Cap-hit behavior: docs say hosted jobs are blocked at start ("The job was not started because recent account payments have failed or your spending limit needs to be increased."); a stuck-queued variant was observed locally 2026-07-18 (github-iac ADR 0008). Conflict unresolved — Phase 5 characterizes it. Vendor-docs-only claim; no independent corroboration channel exists.
  • Self-hosted remains free: the announced per-minute self-hosted charge was postponed indefinitely.
  • Included minutes: Free plan 2000/mo, Team 3000/mo for private repos (verify the org's plan at implementation). ubuntu-slim pool-draw behavior and OS-multiplier mechanics are open gaps — decidable cheaply in the first days after the Aug-1 rollover.
  • Unmatched-label queue: jobs on an unroutable label auto-fail at 24h (vendor-docs-only).

Verdict gate

Feasibility is MEDIUM-confidence until Phase 0's empirical probe passes. Everything downstream is gated on it.

Phases

  • Phase 0 (GATE): extend the observer GitHub App with billing read; probe the usage endpoint with an App token; record the working permission name (docs never name it). A 200 settles feasibility HIGH.
  • Phase 1: scheduled poll (every 2–6h) reads usage + budget state, caches routing state in an org variable (private-pool consumption %, any private paid spend).
  • Phase 2: new select-runner policy prefer-hosted-while-free: route hosted only while consumption < ~85% AND no private paid spend; fleet otherwise and on any error (fail toward the fleet). The selector job itself stays fleet-routed — a hosted selector dies with its own cap.
  • Phase 3: consumer rollout. Sequencing dependency: gates: private-caller hosted fallback in semantic-pr / do-not-merge-gate / pr-issue-linkage reusables #148's pin-bump rollout completes first.
  • Phase 4: Aug-1 rollover tests: ubuntu-slim pool draw; OS-multiplier mechanics. Shared window with Decide: fail-closed publisher recovery shape on fleet outage after the fleet-label flip (carve-out vs sentinel) #241's revisit trigger — one test session covers all three.
  • Phase 5: characterize actual cap-hit behavior (fail-at-start vs stuck-queued) and encode the observed truth in publisher expectations.

Genuinely impossible (design around, don't chase)

Native fallback; readable "budget currently blocking" state; real-time usage; GITHUB_TOKEN-only design; graceful behavior for jobs racing the cap inside the polling window.

References


Filed by an AI agent at the operator's direction (attended queue session, operator-ratified 2026-07-26).

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: highSignificant impact, or blocks an imminent release; staff this cycle.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions