Summary
The host controller (v0.1.24, melo-lap-001) wedged twice within 13 hours. Both times the process stayed alive and the control plane kept answering host status, but the reconcile loop stopped: observed.heartbeatAt froze, no worker was created or retired, and every job for melodic-ubuntu-24.04-x64 sat queued at GitHub while GitHub's own status page reported all systems operational.
Evidence
Wedge 1 (2026-09-04 01:03Z to 01:53Z, PID 14732, uptime about 24 h):
host status --json: controller.phase=ready, observed.heartbeatAt=2026-09-04T01:03:14Z for 50 minutes; three workers reported (idle, busy, busy) while docker ps showed zero ci-runner-* containers. Worker log for dl64halo74o4-2v8 ends at 01:03:30Z with a clean Runner listener exit with 0 return code.
- Process had consumed about 126,899 CPU-seconds over its lifetime (~1.4 cores continuously).
host controller restart hung 240 s waiting for the drain confirmations the dead loop never produced. taskkill /F plus schtasks /Run /TN ci-runner-fleet recovered it; first fresh heartbeat 01:57:36Z, queue drained within two minutes.
Wedge 2 (2026-09-04, PID 206652 from 01:53Z, then PID 89152 after a graceful restart at 13:54Z):
- From about 13:40Z GitHub stopped assigning queued jobs to the scale set although four workers were idle,
capacityAcknowledged=true, maxCapacity=11, totalAssignedJobs=0; 17 github-iac jobs queued for 12 minutes. Controller log in that window shows only worker-retirement-zero-stability-unconfirmed (x10), worker-removal-capacity-stale (x9), one runner-registration-missing, one unregistered-worker-became-busy.
host controller restart completed cleanly this time (capacity-zero drain accepted, exit code 75, receipt verified, replacement PID 89152 started 13:54:51Z).
- The replacement never left
phase=disabled although desired.mode=enabled: observed.heartbeatAt stayed at 13:54:49Z for 9+ minutes, both listeners at capacity=0, zero workers, controller log contains only the controller-started line. Process CPU: 11.05 s in a 10 s window (busy loop), 23 threads, 148 MB, and the only TCP connection was the local OTEL exporter on 127.0.0.1:4317 (no GitHub or Docker API traffic). host enable --wait hung 180 s. host doctor reported every check PASS except the advisory pending-OS-reboot WARN.
Host: Windows 11 Pro 26200, Docker Desktop responsive throughout (docker version/docker info under 1 s), AC power, 37 GiB free memory, host CPU otherwise idle.
Fix
Unknown; needs the reconcile loop's own diagnostics. Suggested first steps:
- Make the loop's liveness observable: a stall of
observed.heartbeatAt beyond reconcileInterval x N should be surfaced by host doctor as a FAIL, not PASS (it showed PASS with age=2m17s maximumAge=26m10s while the loop was already dead).
- Capture a goroutine dump (or the equivalent) on
host controller restart timeout and on a heartbeat stall, written under diagnostics/.
- Check the startup path for a busy-wait: the replacement process spun at ~1 core with no network activity and no log lines after
controller-started.
Verification
- A controller that has stalled for longer than the documented threshold is reported by
host doctor as FAIL with the stall age.
- A diagnostic archive exists for each stall and names the blocked goroutine or loop.
Related
- Wedge 1 recovery commands and timings are recorded in the operator's session notes for 2026-09-04.
- Config:
%LOCALAPPDATA%\ci-runner\config.yaml (schemaVersion 2, reconcileInterval: 5s, startupTimeout: 2m, power.policy: ac-only).
Summary
The host controller (v0.1.24, melo-lap-001) wedged twice within 13 hours. Both times the process stayed alive and the control plane kept answering
host status, but the reconcile loop stopped:observed.heartbeatAtfroze, no worker was created or retired, and every job formelodic-ubuntu-24.04-x64sat queued at GitHub while GitHub's own status page reported all systems operational.Evidence
Wedge 1 (2026-09-04 01:03Z to 01:53Z, PID 14732, uptime about 24 h):
host status --json:controller.phase=ready,observed.heartbeatAt=2026-09-04T01:03:14Zfor 50 minutes; three workers reported (idle,busy,busy) whiledocker psshowed zeroci-runner-*containers. Worker log fordl64halo74o4-2v8ends at 01:03:30Z with a cleanRunner listener exit with 0 return code.host controller restarthung 240 s waiting for the drain confirmations the dead loop never produced.taskkill /Fplusschtasks /Run /TN ci-runner-fleetrecovered it; first fresh heartbeat 01:57:36Z, queue drained within two minutes.Wedge 2 (2026-09-04, PID 206652 from 01:53Z, then PID 89152 after a graceful restart at 13:54Z):
capacityAcknowledged=true,maxCapacity=11,totalAssignedJobs=0; 17 github-iac jobs queued for 12 minutes. Controller log in that window shows onlyworker-retirement-zero-stability-unconfirmed(x10),worker-removal-capacity-stale(x9), onerunner-registration-missing, oneunregistered-worker-became-busy.host controller restartcompleted cleanly this time (capacity-zero drain accepted, exit code 75, receipt verified, replacement PID 89152 started 13:54:51Z).phase=disabledalthoughdesired.mode=enabled:observed.heartbeatAtstayed at 13:54:49Z for 9+ minutes, both listeners atcapacity=0, zero workers, controller log contains only thecontroller-startedline. Process CPU: 11.05 s in a 10 s window (busy loop), 23 threads, 148 MB, and the only TCP connection was the local OTEL exporter on 127.0.0.1:4317 (no GitHub or Docker API traffic).host enable --waithung 180 s.host doctorreported every check PASS except the advisory pending-OS-reboot WARN.Host: Windows 11 Pro 26200, Docker Desktop responsive throughout (
docker version/docker infounder 1 s), AC power, 37 GiB free memory, host CPU otherwise idle.Fix
Unknown; needs the reconcile loop's own diagnostics. Suggested first steps:
observed.heartbeatAtbeyondreconcileIntervalx N should be surfaced byhost doctoras a FAIL, not PASS (it showed PASS withage=2m17s maximumAge=26m10swhile the loop was already dead).host controller restarttimeout and on a heartbeat stall, written underdiagnostics/.controller-started.Verification
host doctoras FAIL with the stall age.Related
%LOCALAPPDATA%\ci-runner\config.yaml(schemaVersion 2,reconcileInterval: 5s,startupTimeout: 2m,power.policy: ac-only).