GitHub Actions / developer guide
One variable.
The right runner.
Route private jobs through your provider policy. Keep public jobs on GitHub-hosted runners.
Inspect the routing Organization setup ↓Private reads the variable.
Public takes the fallback.
- Private repository
CI_PRIVATE_RUNNERConfigured runner label → orubuntu-latestwhen unset.- Public repository
ubuntu-latestThe private-only organization variable is not exposed.
The boundary is the variable’s private visibility—not a condition in every workflow.
runs-on: ${{ vars.CI_PRIVATE_RUNNER || 'ubuntu-latest' }}02 / Policy evaluation
Trace the runner decision.
Evaluate providers in order. Select the first with remaining minutes above its reserve. Unknown usage is skipped.
| Order / provider | Included | Used | Remaining | Reserve | Decision |
|---|---|---|---|---|---|
| 1GitHub-hosted | 2,000 | 1,900 | 100 | 100 | Skip · at reserve |
| 2Blacksmith | 3,000 | 850 | 2,150 | 100 | Selected |
| 3Fallback | — | — | — | — | Not evaluated |
blacksmith-4vcpu-ubuntu-2404GitHub is at its reserve. Blacksmith has quota available.Minutes shown use the checked-in quota defaults. The fallback example assumes FALLBACK_RUNNER is configured. Evaluation stops after the first available provider.
What this repository does today
Bootstrap & evaluate
GitHub App authentication verifies organization access and creates the private-only variable without overwriting an existing value. The Python engine evaluates a supplied usage file.
Collect & update
Provider usage collection and writing policy decisions back to the organization variable are separate integration work. The checked-in workflow runs bootstrap—not the full routing loop.
An organization.
An App. A first run.
Fork the gateway into the organization you want to manage. Keep the App credentials in repository settings, never in source.
Read the GitHub App guideFork into your organization
Create an organization-owned fork of runner-gateway.
Create and install a GitHub App
In organization settings, create an App, disable webhooks, and install it on that organization. Generate a private key.
Organization permissions Permission Access Administration Read-only Variables Read and write No repository write permission is required.
Add the credentials to your fork
APP_CLIENT_ID- Repository variable · App client ID
APP_PRIVATE_KEY- Repository secret · generated PEM key
Run Runner Gateway once
Open Actions → Runner Gateway → Run workflow. Bootstrap verifies billing and variable API access, then creates
CI_PRIVATE_RUNNER=ubuntu-latestwithprivatevisibility if it does not exist.Existing variable values are never overwritten by bootstrap.
Bring the usage. Let the policy decide.
Usage collection stays outside the selector. Supply measured minutes, evaluate the rules, and inspect the returned decision.
{
"github": 1250,
"blacksmith": 850
}FALLBACK_RUNNER=other-provider-runner \
python3 runner_gateway.py \
--usage usage.jsonConfigure your actual allowances
Edit config/policy.json. Defaults are 2,000 GitHub minutes and 3,000 Blacksmith minutes, each with a 100-minute reserve. GitHub allowances vary by plan.
Unknown is not available
A provider without supplied usage is skipped. Use an authoritative usage source; do not treat a missing reading as unused quota. An unset fallback runner is skipped too.
Schedule outside the public workflow
Use an external scheduler for collection, evaluation and variable updates. A systemd timer, container cron or cloud job can orchestrate the loop. The repository workflow is not that integration.
Keep the rule in one place.
Configure at the organization level. Keep application workflows simple.