This morning, before launching the forks that refined this note's tickets, I exported an environment variable by hand from outside the script that starts the loop. I set it that way every time I run this project. Five nights ago that variable didn't exist anywhere, and not knowing that cost USD 2.79 and zero finished tickets.
I'm going to wait
In the early hours of September 12th, in the chatbot project, the loop ran in parallel for the first time: two tickets at once, each with its own coordinator. Each coordinator delegated the work to a coder, which started a background task while continuing its own turn. Both wrote almost the same thing before closing: "I'm going to wait for the completion notification," with success status. No error in sight; the process reported that it was waiting.
It wasn't waiting: it had finished, and it wasn't coming back. Ten minutes later — six hundred seconds — the process that actually was waiting cut off both coders with the same message: Background tasks still running after 600s; terminating. Set CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0 to wait indefinitely. The record went untouched. Cost: USD 2.79. Two coordinators, zero tickets done.
Ten minutes
Running something in headless mode — no interactive terminal, claude -p answers once — lets you delegate to an agent "in the background": it starts another one and keeps going with its own work while the second one works apart, and finds out when the second one signals it's done. It's fine for a short question. But the harness ships with a default wait ceiling — ten minutes —, meant for a one-off query, not for a loop that writes, reviews, and fixes code. If it takes longer, the harness doesn't warn that it got tired: it kills the process underneath, and the one above is no longer around to find out.
Eighteen minutes after that run started, the fix arrived: one line of code, plus four lines of comments, in the loop script. The next run finished ten tickets that same morning.
One line, one repo
What's notable isn't the fix itself — the error message suggests it. The same ceiling had already cut off a run in this project one day earlier, with USD 0.81 lost, and it got logged as pending instead of fixed on the spot. It's still pending six days later. Of eleven loop scripts across the ecosystem of projects I work with — nine active ones plus the two templates every new project is born from — only one has the line: the one that suffered it that morning. A project that starts today is born without the fix.
A one-line fix has the same propagation problem as a ten-page convention: if nothing pushes it between repos, someone has to remember to push it. How many one-line fixes do you have sitting in a single place, with the same bug waiting in the rest?
Top comments (1)
The phrase "I am going to wait" is the useful failure signal because it describes intent, not a durable wait. I would require every deferred task to produce a machine-owned handle before the turn can end: job id, wake/subscription id, deadline, and the next action. Then reconcile open handles independently. A sentence cannot keep a process alive, and success should mean the continuation mechanism exists, not that the agent promised to use one.