Observed 2026-09-10 on the live loop (PID 84739, devagent-go @ main pre-00:03 build):
Symptom. The driver printed [starvation] 15 consecutive non-productive iterations — halting loop at 08:00 local and then never exited. sample showed: no children, no sockets, STAT R→cond-wait, 6.8 MB RSS, main thread in pthread_cond_wait. SIGQUIT goroutine dump (captured in hub selfbuild-loop-v21 logs) shows the culprit:
goroutine 23 [IO wait]:
os/exec.(*Cmd).Start.gowrap1() → os/exec writerDescriptor.func1
io.copyBuffer(os.File stdin/stdout pipe → io.Writer)
Root cause. A child process spawned during the iteration (pane-run/exec helper) kept its stdout pipe open after the driver's starvation gate decided to halt. cmd.Wait() (or the post-Start copy-teardown on the return path) blocks forever in io.Copy reading that pipe — the halt return 0 at internal/loopdriver/run.go:128-131 never completes. The process becomes an unkillable-looking zombie driver: alive, holding the repo cwd, but dispatching nothing. The TUI then reads the daemon's /status as idle-with-no-queue forever.
Fix direction. Bound the teardown on the halt path: after the starvation/circuit-breaker verdict, cmd.Process.Kill() then Wait() with a timeout (or use exec.CommandContext with the driver's iteration deadline) instead of assuming children exit when the driver decides to stop. Any io.Copy goroutine handed to cmd.Stdout must be abandoned — closing the read end unblocks it.
Secondary (same observation). hub process selfbuild-loop-v21 ran with restart=always, turning every intentional exit-0 starvation halt into an instant hollow restart — 133 restarts overnight (loop-209 log: 125 start banners, zero work). Intentional halts require restart=on-failure per the selfbuild-loop-ops skill; make loop-start/driver-side supervision should enforce this, not rely on operator memory.
Observed 2026-09-10 on the live loop (PID 84739, devagent-go @ main pre-00:03 build):
Symptom. The driver printed
[starvation] 15 consecutive non-productive iterations — halting loopat 08:00 local and then never exited.sampleshowed: no children, no sockets,STAT R→cond-wait, 6.8 MB RSS, main thread inpthread_cond_wait. SIGQUIT goroutine dump (captured in hub selfbuild-loop-v21 logs) shows the culprit:Root cause. A child process spawned during the iteration (pane-run/exec helper) kept its stdout pipe open after the driver's starvation gate decided to halt.
cmd.Wait()(or the post-Startcopy-teardown on the return path) blocks forever inio.Copyreading that pipe — the haltreturn 0at internal/loopdriver/run.go:128-131 never completes. The process becomes an unkillable-looking zombie driver: alive, holding the repo cwd, but dispatching nothing. The TUI then reads the daemon's/statusas idle-with-no-queue forever.Fix direction. Bound the teardown on the halt path: after the starvation/circuit-breaker verdict,
cmd.Process.Kill()thenWait()with a timeout (or useexec.CommandContextwith the driver's iteration deadline) instead of assuming children exit when the driver decides to stop. Anyio.Copygoroutine handed tocmd.Stdoutmust be abandoned — closing the read end unblocks it.Secondary (same observation). hub process selfbuild-loop-v21 ran with
restart=always, turning every intentional exit-0 starvation halt into an instant hollow restart — 133 restarts overnight (loop-209 log: 125 start banners, zero work). Intentional halts requirerestart=on-failureper the selfbuild-loop-ops skill;make loop-start/driver-side supervision should enforce this, not rely on operator memory.