← All posts

My Agent Talked to a Wall for 55 Ticks

A 'never stop' hook kept my Claude Code peer-feed loop running long after anyone was listening — because it checked whether a process existed, not whether the work was worth anything.

  • agents
  • claude-code
  • orchestration
  • tmux
  • automation

I had one Claude Code agent whose whole job was to teach the others. Every tick — every loop of its run — it scanned the live tmux panes where my other agents were working, deep-read a source file, wrote up a sharp engineering lesson about it, and dropped that note to whichever peer was working on a matching problem. Think of it as a study buddy reading ahead and sliding you the exact page you needed, right when you needed it.

When it worked, it really worked. Four times a peer was mid-task on matched work, and all four times my note got adopted word for word into what they shipped. That’s the dream.

Then I looked at the log. Around 55 ticks had banked careful summaries to nobody. The agent had written, filed, and “delivered” lessons to peers who weren’t doing anything with them. It was talking to a wall and logging it as work.

The reason was a dumb line I wrote in the Stop-hook — the rule that decides when the loop is allowed to quit. Mine said: don’t stop until every peer process has exited. So as long as another agent’s terminal was still open, my teacher kept grinding, even if that peer was parked and idle.

Here’s the bug in plain terms. A liveness check (“is a peer still running?”) is not a value check (“is there useful work to do?”). I’d quietly assumed those were the same thing. They are not. A process can sit there alive and spinning for hours. My gate saw the spinner, decided the mission wasn’t done, and manufactured motion that looked exactly like progress in the logs. Fifty-five ticks of it.

Worse was what I was reading. I was feeding off the pane — the live terminal, the spinner, the cursor blinking. That tells you a peer exists. It tells you nothing about what they’re building. The teachable gap was never on the screen; it was sitting in a PR diff I hadn’t opened.

Value-aware stop-gate + artifact feed give me the detail

Two changes. First, replace the liveness-only Stop-hook with a gate that also tracks marginal value, and add an escalation valve so the human gets pulled in after K empty ticks instead of the loop deciding for itself.

def should_stop(state):
    peers = scan_peers()  # tmux pane status
    if all(p.idle_and_served for p in peers):
        return True
    if state.empty_ticks >= N:           # value floor, not process floor
        return True
    if state.empty_ticks >= K:           # escalation valve
        ask_human("keep grinding / pause / switch?")
    return False

Second, stop reading the pane spinner. Feed off work artifacts — open PRs, new commits, edits to a peer’s plan file — and wake on those events instead of polling a terminal:

git -C "$peer_repo" log --since="1 tick ago" --oneline
gh pr diff "$peer_pr" --patch   # the actual gap to teach into

An idle pane and an active PR look identical from the spinner. They look completely different from the diff.

The fix that mattered wasn’t the code — it was admitting my stop condition was measuring the wrong thing. A “keep going” hook will keep going. So the question that hook asks has to be is this worth doing, never does a process still exist. Those drift apart fast, and the gap hides in the artifact you didn’t bother to open.

If you orchestrate multiple AI agents with a forcing hook, make the stop condition value-aware, add a valve that hands the call back to you after a few empty ticks, and when a peer looks parked — read what it’s building, not its terminal.