The supervisor died of its agent's scrollbar, eight times in seventy seconds
A queue rang a doorbell and the process supervisor fell over. Not the agent — the daemon supervising it, and every other agent under it.
The daemon keeps a sanitized log per agent by feeding each PTY through a terminal emulator. The agent is a TUI. When a todo arrived it drew a channel event and scrolled; to do that it had set a scroll region taller than its 80×24 terminal — ESC[1;30r on a 24-row screen — and then sent ESC[9S. The emulator’s scroll-up handler indexed row 24 of a 24-row buffer: index out of range [24] with length 24. That call was on the PTY reader goroutine. Unrecovered, a panic there is a panic in the daemon.
So the supervisor exited with status 2, and its children went with it.
Then the box did what it was built to do. systemd restarted the daemon. The daemon restarted the agent. The agent reconnected to the queue and opened a fresh notification stream. The queue, on a fresh stream, rings every pending todo in scope — the one that had just killed everything was still pending, because nobody had claimed it. The agent drew it. The emulator indexed row 24. Restart counter: 8, in 70 seconds, before I stopped the unit by hand.
Nothing in the loop was wrong on its own. Restart-on-failure is right. Reconnect is right. Ring-the-backlog-on-reconnect is right — it is what makes a missed doorbell harmless. The agent’s escape sequence is legal. Together they made a machine whose only stable state was “crashing,” and whose every recovery mechanism fed the next crash.
Two lines to look at in your own fleet. First: anything that interprets a child’s output — a terminal emulator, a log parser, a JSON decoder on a pipe — is inside that child’s blast radius. A recover around the emulator write, dropping the frame and rebuilding it, fired 14 times in the first minute afterwards and the daemon’s uptime was unbroken. Second: a durable queue that re-rings on reconnect has no way to tell “this consumer was offline” from “this consumer dies on this todo.” It needs a ring budget per todo, the same way it already has an attempt budget per claim. Mine does not have one yet.
The reproducer fits in a test: vt.NewEmulator(80, 24), write \x1b[1;30r\x1b[9S. It also panics on M, L and T. The root cause is the emulator not clamping a scroll region to the screen; the daemon fix is upstream as a pull request. The agent, throughout, was fine.