Runner
Overview
GitHub Actions self-hosted runner management. Two modes:
- status: Display health, version, and host metrics for all runners
- deploy: Deploy or update runners on the CI host
status
Surfaces per-runner health (GitHub API + Docker inspect) and host-level disk metrics for all OmniNode self-hosted GitHub Actions runners. Alerts for degraded conditions are shown at the top of the output when triggered.
Quick Start
/runner status
No arguments required. The skill queries the GitHub API and SSH-inspects the CI host
(<onex-host>, configured via ONEX_HOST) automatically.
Output Format
When all runners are healthy:
Runner Status — 2026-03-23
+--------------------+--------+--------------+-------------+----------+------------+---------+---------+
| Runner | Status | Runner Group | Runner Ver | gh Ver | kubectl | uv | Uptime |
+--------------------+--------+--------------+-------------+----------+------------+---------+---------+
| omninode-runner-1 | idle | omnibase-ci | 2.323.0 | 2.67.0 | v1.32.1 | 0.6.2 | 5m |
| omninode-runner-2 | busy | omnibase-ci | 2.323.0 | 2.67.0 | v1.32.1 | 0.6.2 | 5m |
| omninode-runner-3 | idle | omnibase-ci | 2.323.0 | 2.67.0 | v1.32.1 | 0.6.2 | 5m |
| ... | ... | ... | ... | ... | ... | ... | ... |
| omninode-runner-10 | idle | omnibase-ci | 2.323.0 | 2.67.0 | v1.32.1 | 0.6.2 | 5m |
+--------------------+--------+--------------+-------------+----------+------------+---------+---------+
Host metrics (<onex-host>):
/ disk: 38% used (650 GB / 1.8 TB)
Docker build cache: 0 B
When alerts are triggered:
ALERTS
[OFFLINE] omninode-runner-9 has been offline for 12 minutes (threshold: 5m)
[DISK] / at 74% (threshold: 70%)
Runner Status — 2026-03-01 14:23:07 UTC
...
Per-Runner Data
The following fields are shown for each runner:
| Field | Source | Notes |
|---|---|---|
| Runner name | GitHub API | Container hostname / runner display name |
| Status | GitHub API | idle, busy, or offline |
| Runner group | GitHub API | Must be omnibase-ci; flagged if different |
| Runner binary version | Docker image label org.omninode.runner.version |
Set in Dockerfile ARG |
| gh CLI version | Docker image label org.omninode.gh.version |
Set in Dockerfile ARG |
| kubectl version | Docker image label org.omninode.kubectl.version |
Set in Dockerfile ARG |
| uv version | Docker image label org.omninode.uv.version |
Set in Dockerfile ARG |
| Container uptime | docker ps --format "{{.Status}}" on host |
Via SSH |
Label Keys
The Docker image labels are defined in the runner Dockerfile. The skill reads them deterministically using these exact keys:
org.omninode.runner.version
org.omninode.gh.version
org.omninode.kubectl.version
org.omninode.uv.version
Read via:
# SSH to CI host, inspect the runner container (set ONEX_HOST to runtime host address)
# Runner names are discovered from the GitHub API (Step 1), not hardcoded
ssh <user>@<onex-host> "docker inspect omninode-runner-9 --format '{{json .Config.Labels}}'"
Host-Level Metrics
In addition to per-runner data, the skill collects host-level metrics from the CI host:
Disk Usage (/var/lib/docker)
ssh <user>@<onex-host> "df -h /var/lib/docker"
Reports: total size, used, available, and usage percentage. Alerts if usage >= 70%.
Docker Build Cache Size
ssh <user>@<onex-host> "docker builder du --verbose 2>/dev/null | tail -1"
Reports total build cache size. No alert threshold -- informational only.
Alert Conditions
Alerts appear at the top of the output (before the status table) when any condition is met:
| Condition | Threshold | Alert Message |
|---|---|---|
| Runner offline | > 5 minutes | [OFFLINE] {runner} has been offline for {N} minutes (threshold: 5m) |
| Disk usage | >= 70% | [DISK] /var/lib/docker at {N}% (threshold: 70%) |
| Runner version lag | > 2 releases behind latest GitHub release | [VERSION] {runner} running {current} -- latest is {latest} ({N} releases behind) |
Runner Version Check
The skill compares the runner binary version (from Docker image label org.omninode.runner.version)
against the latest release from the GitHub Actions runner release feed:
gh api repos/actions/runner/releases/latest --jq '.tag_name'
If the running version is more than 2 releases behind the latest, an alert is emitted. Version comparison uses semantic versioning (major.minor.patch).
Offline Detection
"Offline" status is reported by the GitHub API. Duration is estimated from the
last_activity timestamp on the runner object. Alert fires when now - last_activity > 5 minutes.
Status Implementation Steps
When status is invoked, execute the following:
Step 1: Query GitHub API for runner list
gh api orgs/OmniNode-ai/actions/runners --jq '.runners[]'
This returns per-runner: id, name, status (online/offline), busy,
runner_group_name, labels, and timestamps.
Step 2: SSH to CI host -- collect Docker labels and uptime
# For each runner discovered from the GitHub API in Step 1:
ssh <user>@<onex-host> "docker inspect omninode-runner-9 --format '{{json .Config.Labels}}' && docker ps --filter name=omninode-runner-9 --format '{{.Status}}'"
Parse org.omninode.runner.version, org.omninode.gh.version, org.omninode.kubectl.version,
org.omninode.uv.version from the labels JSON.
Step 3: Collect host metrics
ssh <user>@<onex-host> "df -h /var/lib/docker && docker builder du 2>/dev/null | tail -1"
Step 4: Check latest runner version
gh api repos/actions/runner/releases/latest --jq '.tag_name'
Compare against the org.omninode.runner.version label from each container.
Step 5: Evaluate alerts
For each alert condition (see Alert Conditions above), check and collect triggered alerts.
Step 6: Render output
Print the output in the format shown in the Output Format section:
- Alert block (if any alerts triggered) -- shown first, with
ALERTSheader - Timestamp line
- Per-runner table (all 7 fields)
- Host metrics block
deploy
Deploy or update self-hosted GitHub Actions runners on the OmniNode CI host (<onex-host>, configured via ONEX_HOST)
with a single command from Claude Code.
Quick Start
# Preview what would change (no deploy)
/runner deploy --dry-run
# Deploy or update runners (uses cached Docker image)
/runner deploy
# Force Docker image rebuild (use after Dockerfile changes)
/runner deploy --rebuild
Prerequisites
Before deploying, verify:
GitHub org-level token scope -- check with:
gh auth status # Must show: Token scopes: ...admin:org (or manage_runners:org)SSH key for the CI host in agent (
<onex-host>) -- check with:ssh-add -l # Must show a key fingerprint; if empty run: ssh-add ~/.ssh/id_ed25519 (or your key)Note: SSH key path is not configured via env var -- it must be loaded into the running ssh-agent. No local Docker client is required; all operations run remotely via SSH.
If either check fails, resolve before running /runner deploy.
Dry-Run Mode (--dry-run)
When --dry-run is specified, no changes are made. The skill will:
- SSH to the CI host (
<onex-host>) and rundocker compose configto display the current runner compose configuration - Compare the Dockerfile
ARGversions (Node, GitHub Actions runner) against the running container image labels to detect whether a rebuild would be triggered - Print which containers would be recreated by the deploy
Example dry-run output:
[DRY RUN] Runner deploy — <onex-host>
Current compose config:
Services: omninode-runner-9
Image: omnibase-ci-runner:latest (built 2026-03-09)
Version check:
Dockerfile ARG RUNNER_VERSION=2.331.0
Running image label: runner_version=2.331.0 up to date
Dockerfile ARG NODE_VERSION=20
Running image label: node_version=20 up to date
Containers that would be recreated: none (image up to date)
Use /runner deploy to apply, or /runner deploy --rebuild to force image rebuild.
Normal Deploy Behavior
When invoked without --dry-run:
- Prerequisite check -- verifies
gh auth statusshows org-level token scope and SSH key is available in agent - Deploy -- SSH to the CI host (
<onex-host>) and invokedeploy-runners.sh(with--rebuildflag forwarded if specified) - Status check -- surfaces the final runner status table via
runner statusso you can confirm all runners registered successfully
With --rebuild
Forces the Docker image to rebuild on the remote host before bringing containers up. Use this when:
- Runner version (
RUNNER_VERSIONARG) has been bumped in the Dockerfile - Node/tool versions have changed
- The base image (
ubuntu:22.04) needs refreshing - Any other Dockerfile change has been made
Without --rebuild, the deploy uses the cached image (faster, safe for config-only changes).
Script Location
The underlying deploy script lives in the omnibase_infra repository:
omnibase_infra/scripts/deploy-runners.sh
This skill wraps that script to make runner deployment accessible as a one-command operation
from any Claude Code session without needing to navigate to the omnibase_infra worktree
or remember the SSH invocation syntax.
Deploy Implementation Steps
When deploy is invoked, execute the following:
Step 1: Check prerequisites
# Verify GitHub token has org scope
gh auth status 2>&1 | grep -E "(Token scopes|admin:org|manage_runners)"
# Verify SSH key is loaded
ssh-add -l
If prerequisites fail, report clearly which check failed and how to fix it. Do not proceed.
Step 2 (dry-run only): Show compose diff
# SSH to host and show compose config (set ONEX_HOST to runtime host address)
ssh <user>@<onex-host> "cd ~/.omnibase/runners/docker && docker compose -f docker-compose.runners.yml config"
# Compare Dockerfile ARG versions to running image labels (use runner name from GitHub API)
ssh <user>@<onex-host> "docker inspect omninode-runner-9 --format '{{.Config.Labels}}' 2>/dev/null || echo 'container not running'"
ssh <user>@<onex-host> "grep -E '^ARG (RUNNER|NODE)_VERSION' ~/.omnibase/runners/docker/runners/Dockerfile"
Print a clear summary of: current state, what would change, and which containers would be recreated. Exit without making changes.
Step 3 (deploy only): Run deploy-runners.sh
REBUILD_FLAG=""
# If --rebuild was specified:
REBUILD_FLAG="--rebuild"
ssh <user>@<onex-host> "cd ~/omnibase_infra && bash scripts/deploy-runners.sh ${REBUILD_FLAG}"
Capture and display the full output. If the script exits non-zero, report the error and stop.
Step 4 (deploy only): Surface status
Invoke runner status to confirm all runners registered successfully:
/runner status
Error Handling
| Error | Action |
|---|---|
gh auth status shows no org scope |
Stop; print: "GitHub token missing org scope. Run: gh auth refresh -s admin:org" |
ssh-add -l returns "The agent has no identities" |
Stop; print: "No SSH key in agent. Run: ssh-add ~/.ssh/<your_key>" |
| SSH connection refused/timeout | Stop; print: "Cannot reach CI host. Check VPN/network." |
deploy-runners.sh exits non-zero |
Stop; print full script output; suggest --rebuild if image issue suspected |
Runner fails to register (seen in runner status) |
Print warning; link to GitHub Actions runner registration docs |
gh api returns 401 / 403 |
Stop; print: "GitHub API auth failed. Run: gh auth status" |
Container not found (docker inspect error) |
Show runner row with N/A for label fields; add [MISSING] note |
docker builder du unsupported |
Skip build cache line silently; show -- in output |
See Also
omnibase_infra/scripts/deploy-runners.sh-- Underlying deploy scriptomnibase_infra/docker/runners/Dockerfile-- Runner image definitionomnibase_infra/docker/docker-compose.runners.yml-- Runner compose config- GitHub Actions self-hosted runner docs: https://docs.github.com/en/actions/hosting-your-own-runners