Policies for agents that sit there sleeping instead of checking anything.
failproofai policies add xevrion/wait-guardAn agent that runs sleep 300; echo timer has spent five minutes and learned
one thing: that five minutes passed. Nothing was polled, no condition was
tested, and if whatever it was waiting for finished in twenty seconds it waits
out the other two hundred and eighty anyway.
I found this in my own failproofai audit and went looking for how bad it
was. Across 538 commands containing a sleep, the unobserved ones came to about
four hours of blocked wall clock, and the worst run was sixteen consecutive
timers in a single session.
| policy | default |
|---|---|
warn-unobserved-wait — a long sleep, or a counted loop, that watches nothing |
on |
warn-unbounded-wait-loop — while/until around a sleep with no attempt limit |
on |
Both are instruct rather than deny. Sometimes a fixed sleep really is what
you want (deliberate rate limiting), and blocking the call leaves the agent
with no way forward. The instruction names the alternative instead: a bounded
retry that polls the real condition, or timeout around the work itself.
The thing that decides whether a wait is wasteful is not how long it is, it is whether anything is being observed. These are all fine and none of them fire:
npm run dev & sleep 3; curl -s localhost:3000 # settle, then check
sleep 60; curl -s localhost:3000/health # long, but it polls
sleep 120; cat build.log # long, but it reads
for i in $(seq 1 30); do curl -s x && break; sleep 1; done # bounded retryThese do:
sleep 300; echo timer # nothing observed
for i in $(seq 1 10); do sleep 150; echo "tick $i"; done # counts, observes nothing
until grep -q ready log.txt; do sleep 15; done # no attempt limit, can hang foreverThat middle one is worth calling out. A counted loop is bounded, so it always
terminates, but bounding a wait is not the same as watching something: seventeen
of those in my history cost 267 minutes between them, which is more than the
plain timers did. It is a twenty-five minute sleep wearing a for loop.
The 30s threshold comes from the same corpus. Sleep durations there run p50=4s
and p75=22s, so 30s sits above three quarters of ordinary shell usage. Below it
you are almost always looking at settle-time after starting a process, and a
policy that fires on sleep 2 gets switched off the same day.
31 cases through failproofai's runner, weighted toward the things that must stay silent, including one replayed as Codex:
node "$SKILL_DIR/scripts/test-policy.mjs" --policy waiting-policies.mjs --cases cases.jsonbacktest.mjs replays both policies over local Claude Code history. Against
mine: 538 commands containing a sleep, 116 instructed (21.6%), 422 left alone,
262 minutes of wall clock inside the ones it caught. Reading the longest sleeps
it deliberately ignored, every one of them runs a command or reads a file
afterward, which is the line the policy is drawing.