Self-Hosted Runner Box Operations
Self-Hosted Runner Box Operations (.113 pool)
Section titled “Self-Hosted Runner Box Operations (.113 pool)”The self-hosted pool (self-hosted, omni-release on all eight runners; omni-build on two) runs on the .113 box.
Measured 2026-08-28 (v3.8.50 postmortem, Parte III):
| resource | value | what it means for scheduling |
|---|---|---|
| RAM / CPU | 31 GB / 32 cores (was 16 GB when this doc was first written) | one next-build peaks at ~14 GB → 2 concurrent heavy builds saturate the box, 3 take it down (2026-08-28 06:42Z: load 56, two jobs lost) |
| swap | 15 GB | it swapped its way through the v3.8.50 publish; pressure shows in /proc/pressure/memory |
/tmp |
12 GB tmpfs = RAM | anything parked there is memory; leftovers are swept after 3 h |
| disk | 188 GB | _work checkouts of 8 runners reach ~70 GB with no cap |
| runners | 6 listeners: 4 OmniRoute (1 omni-build + 1 omni-release-only + 2 omni-light) + OmniHeuris + OmniMind |
all share the memory above; omniroute-113-3/-4/-7/-8 are disabled (systemctl enable --now brings one back) |
Install the janitor (one-time, on the box)
Section titled “Install the janitor (one-time, on the box)”scp scripts/ops/runner-janitor.sh root@192.168.0.113:/opt/omniroute-ops/runner-janitor.shssh root@192.168.0.113 'chmod +x /opt/omniroute-ops/runner-janitor.sh; apt-get install -y lsof'# cron (root): every 30 min, log to /var/log/runner-janitor.log*/30 * * * * MAX_ACTIVE_RUNNERS=6 /opt/omniroute-ops/runner-janitor.sh >> /var/log/runner-janitor.log 2>&1lsof is required: the janitor proves a path is idle with one snapshot of open
files before removing it, and without the tool it removes nothing and says so
(exit 1). Try any change with --dry-run first — it prints exactly what it would
do and touches nothing.
What it does every run: sweeps our own leftovers (runner-*, omniroute-*,
next-build*, e2e-build.tar.gz) after 3 h on tmpfs and 24 h on disk
_work/_temp; kills a next-build older than 75 min (no job runs that long — on
2026-08-27 one ran 70 min after GitHub had declared its job lost); prunes 48 h-old
checkouts of runners whose unit is stopped; alerts on disk ≥ 85 %, memory PSI
full/avg60 ≥ 10 %, and more listeners than MAX_ACTIVE_RUNNERS (with an
omniroute/other breakdown). Exit 1 = attention needed; read the log.
Runner units: KillMode
Section titled “Runner units: KillMode”The runner’s default KillMode=process leaves Runner.Worker → npm → next-build
alive when a unit is stopped or restarted — an orphan build keeps eating RAM and
CPU with no job attached. Every OmniRoute unit carries a drop-in
(/etc/systemd/system/actions.runner.diegosouzapw-OmniRoute.<name>.service.d/10-killmode.conf)
with KillMode=mixed: SIGTERM to the listener first, SIGKILL to the whole cgroup at
TimeoutStop. It takes effect on the unit’s next restart — restart one runner at
a time, only when idle, with the idle check and the restart in the same command.
Operating rules
Section titled “Operating rules”- Heavy-build ceiling: ONE at a time — enforced by label (since 2026-08-29). Every job
that runs a full
next buildtargets[self-hosted, omni-build], and onlyomniroute-113-5carries that label (added through the runners API — no re-registration):ci.ymlBuild,npm-publish.ymlpublish, bothnightly-release-greenvalidations, anddocker-publish.ymlamd64 (hosted 7 GB ResourceExhausted this tree — #11976). The arm64 Docker leg stays onubuntu-24.04-arm(no ARM box) with webpack. Docker amd64 also uses webpack: Turbopack on this tree panicked inside BuildKit (TurbopackInternalError: there must be a path to a root, run 33253576569) even with 31 GB; the same tree’s arm64 webpack build on hosted ARM succeeded.docker-publishamd64 shares theheavy-build-mainconcurrency group withci.ymlBuild(cancel-in-progress: false) so it queues on the one slot. Docker Engine must be onomniroute-113-5(docker infois the first step of the publish job). Two was the previous ceiling and it was wrong for 31 GB: on 2026-08-29 17:26 UTC two concurrentnext-builds (15.4 GB + 17.2 GB RSS) drove the box to 5 GB free with 4 GB of swap in use and the kernel OOM-killed one of them — systemd booked the kill on the other runner’s unit,runsvc.shSIGKILLed that listener, and the job on it died with “The runner has received a shutdown signal” (same text as a hosted-runner OOM).omniroute-113-6keepsomni-releaseonly. Heavy builds frommainmerges, PRs and the nightly now serialize on one slot; the queue is the price. The second slot comes back the day the Proxmox VM gets more RAM (48–64 GB):gh api -X POST repos/<repo>/actions/runners/<id of omniroute-113-6>/labels -f 'labels[]=omni-build'. - Light pool:
omni-light(2026-08-29, #11965).omniroute-113andomniroute-113-2carryomni-lightfor jobs that need a backend-onlynext build(~5–6 GB) but not a full one: the nightly Schemathesis, promptfoo, garak and axe-a11y jobs. They ran on the hosted 7 GB runner and died onrelease/v3.8.51with nobody watching. Worst case on the box is 2 heavy + 2 light ≈ 30 + 12 GB — over 31 GB of RAM, inside the 16 GB of swap; the real fix for headroom is more RAM on the Proxmox VM (tomni-proxmox-113), which turns the label ceilings into 3 heavy + 2 light. - Fewer listeners on purpose. Four OmniRoute units were disabled on 2026-08-29 — with only
ci.ymlBuildand the nightlies using the box, 8 listeners were idle and each extra one is a potential 14 GB tenant. The janitor ceiling is 6 (MAX_ACTIVE_RUNNERS=6in cron): it counts everyRunner.Listeneron the box, and OmniHeuris + OmniMind add two to our four. - Never clean
/tmpor_workby hand while any runner is busy. A check-then-delete with a gap between the two is how a live Build job lost its_workon 2026-08-27. The janitor does the check and the removal in one step; let it. - Stopping a runner mid-job cancels the job (observed live):
systemctl stoponly when its listener has noRunner.Workerchild — and do it in one command. - Workflows must not park artefacts in
/tmp(it is RAM). Download to$RUNNER_TEMP(on disk, per runner) — the 1.3 GBnext-buildartefact took 27–32 minutes to land on the tmpfs and 2 minutes to upload from disk. - The
.15VPS is homologation-only — never runs CI runners.
HagiCode
HagiCode is an agentic coding workspace. Rich, convenient UI interactions make planning, agent execution, and project work approachable and easy to navigate, creating an engaging development experience.
Explore an engaging development experience through rich, convenient UI interactions.
- SmartOpenSpec workflows turn intent into an executable path, from proposal to design, tasks, and a shipped change.
- EfficientMulti-agent execution keeps research, implementation, and review moving in parallel, across several repositories and the Agent CLIs you already use.
- InterestingRich, convenient UI interactions make planning, agent execution, and project work approachable and easy to navigate.
Download for Windows
Share HagiCode
Turn ideas into shipped software.
HagiCode is an agentic coding workspace. OpenSpec workflows plan the change, multi-agent execution carries it out, and Hero Scenario makes the long run visible, so ideas become shipped software.
- Smart
- OpenSpec workflows turn intent into a path from proposal to shipped change.
- Efficient
- Multi-agent execution runs research, build, and review in parallel.
- Fun
- Rich UI interactions make planning, agent work, and projects easier to follow.
© 2026 HagiCode | Powered By hagilight-starlight@0.21.0 | Branch: main | Commit: de60bcd | Built:

