"I won't post without your approval" — my dutiful AI died after two minutes
I run an AI agent (Claude Code) that produces content and takes it all the way to publishing on external platforms, on its own. I had exactly one rule. Publishing is irreversible, so right before it posts, it must always get my OK. Anything you publish or send to the outside world lives in a place where deleting it doesn't help — a copy survives. So "make things" it can do freely, but one step before "ship it," it has to get a human (me) to approve. The approval goes to a chat app on my phone (Telegram), and I reply "approve" or "reject." Until then, nothing goes out. So on this particular day, the agent finished building everything, got right up to the approval step, and I walked away. Something like "reply when the approval lands and it'll post itself." That's how relaxed I was about it. I came back, and nothing had been posted An hour or so later I came back. Nothing posted. It hadn't crashed with an error. The material was sitting there, finished. And yet the publishing — just that — hadn't happened. I dug through the logs, and the trick revealed itself. The process waiting for approval had been force-killed after two minutes. Claude Code puts roughly a two-minute execution cap on a command you run in the foreground. For a normal build or test, that's plenty. But what I'd asked it to do was "wait up to 30 minutes for a human to reply in chat." The time a human takes to answer and the lifespan a tool grants a command were wildly out of balance. A process that meant to wait 30 minutes was getting guillotined at two. And here's the quietly brutal part: because the listener got killed, even if I had replied "approve" afterward, there was nobody left to hear it. The answer just vanishes into the air. It's like shouting "Approved!" at an unstaffed reception desk. There's a quiet tug-of-war hiding here Boiled down, two demands were colliding head-on. Humans answer slowly. If I've stepped out, I want it to wait 30 minutes, an hour, whatever. The tool assumes short-lived commands. It dislikes a process that blocks for a long time, so it cuts it off. Write the naive "wait in the foreground until approval arrives," and the one doing the waiting (the agent) gets stabbed by the tool and dies. The act of waiting had become an act of suicide. I hadn't seen that. Think of it like an after-hours delivery clerk. The package arrives. But the receipt needs the homeowner's (my) OK. The homeowner is out. The more dutiful the clerk, the more they stand there and wait rather than forging the signature. Then their shift limit (two minutes) hits, and they're sent home. The package is left dangling. Dutiful. So dutiful it dies. How I fixed it Three things, roughly. 1. Kick the approval-wait out into a "background job" Waiting in the foreground is what gets you killed at two minutes. So I threw the approval-wait out as a background job. Background work survives across conversation turns, and when it finishes (= a reply arrives), a notification fires. The agent doesn't have to sit glued to the desk the whole time — it gets woken up when the answer comes. It stopped camping at the reception desk and got a doorbell instead. 2. Persist state to disk so it can resume even if the whole session dies If I close the Mac, or the session ends completely, the background listener dies with it. I gave up on covering that — and in exchange, I write down "this task is currently parked at the 'waiting for approval' point" in a state file. Next time I open a session, it resumes from that cursor. Long gaps I settled for as "pick it up on the next launch." 3. An idempotency marker to prevent double-posting The scariest case is "the post succeeded, but it crashed before recording that." The resumed agent thinks "I haven't posted yet" and posts a second time. For an irreversible operation, that's fatal. So right before publishing, I drop a single marker that says "about to ship." If it crashes mid-way, on resume it reads the marker and checks the actual external thing — "is this really already published?" — before proceeding. When it can't be sure, it doesn't repost on its own; it asks the human. One more quietly important thing: don't over-resend. If it re-fires the approval request every time the listener dies, the chat fills up with the same nag. I know the person is away, so it notifies cleanly once and defers the rest to next time. In the end, this is about designing so the safety boundary never gets shortcut Technically it's just "wait in the background and wake on a notification." But I don't think that's the real point. The purpose of an approval gate is to stop irreversible operations from happening on their own while the human is absent. Loosen that even once and there was no point putting the gate there at all. Which means the designer's job is to make "wait safely, forever if you have to" cheap. When waiting is a hassle and costs a lot, people lean toward "ah, just ship it." Same with the agent — if it's bad at waiting, you end up building it to charge ahead rather than wait. So an agent that's good at waiting is, in fact, the better one. Pick the dutiful agent that keeps waiting over the impatient one that doesn't. Just — so that dutifulness doesn't get it killed by the tool, we take care of the how of the waiting on our side. When you leave a package at an empty house, you don't want a courier who forges the signature. You want the one who leaves a slip and comes back. All I did this time was retrain the agent to be that kind of courier. That's the whole story.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to