Field notes · 2026-08-25 · RunVouch
Claude Code Routine failed silently? How to know your scheduled agent actually ran
Green does not mean done. The 4 ways a scheduled Claude Code run fails with no error, and heartbeat plus evidence checks that catch each one.
The Claude Code Routines documentation contains one sentence I wish I had read two years earlier, before I started running agents and a trading bot on a schedule. From code.claude.com/docs/en/routines:
"A green status in the run list means the session started and exited without an infrastructure error. It does not mean the task in your prompt succeeded. Open the run to read the transcript and confirm what Claude actually did."
That is the whole problem with unattended agents in three sentences. Green is a statement about the container, not about your task. The same is true of cron's exit code, n8n's execution list, and every "succeeded" badge I have ever trusted.
RunVouch is a dead man's switch, cost cap and outcome check for unattended AI agents: it expects a heartbeat per run, alerts the moment a run or a day crosses a dollar cap, and tells you when a run is missed, stalled, looping or produced no evidence.
Four ways a scheduled agent fails without an error
I hit all four with Claude Code, a headless claude -p cron, and a bot that placed real orders. None of them produced a red status.
1. It never started. The machine was asleep. The session-scoped scheduler was not running: Claude Code's own docs note that in-session tasks "only fire while Claude Code is running and idle" and that there is "no catch-up for missed fires" (scheduled-tasks, Limitations). Routines may "start a few minutes after the scheduled time due to stagger", which is fine, unless your alert threshold is tighter than the stagger. Nothing ran, so nothing failed, so nothing told you.
2. It started, then hung. A tool call waiting on a network that was blocked, an MCP server that never answered, a subprocess waiting for stdin. The process exists. The watchdog that checks "is the PID alive" is satisfied. My trading bot once sat for 31 hours holding an open position because a websocket reconnect never completed and the reconnect loop had no timeout.
3. Exit 0 with no output. The run finished cleanly and wrote nothing. Claude decided the task was already done, or refused, or the output path was wrong. In Routines, "blocked network requests, missing connector tools, and task-level failures all surface" in the transcript, not in the status indicator. In a bare cron there is not even a transcript unless you saved one.
4. It produced the wrong output. The report was written, but it is yesterday's report, or an empty template, or 40 bytes where it is normally 40 KB. This is the one I find hardest to catch by hand, because the file exists and the timestamp is fresh.
Notice that only the second one is even detectable by looking at the process. The other three require knowing what the run was supposed to produce.
How to know your scheduled Claude Code task actually ran
Two mechanisms cover the four cases. Neither is clever.
A heartbeat with a deadline. The agent tells a separate service "I started", "I am still going", "I finished". The service, not the agent, holds the clock. If the start heartbeat does not arrive within the cadence, that is MISSED (case 1). If it starts and heartbeats stop mid-run, that is STALLED (case 2). If the finish heartbeat carries a non-zero exit, that is FAILED. The important property is that silence is an alert. A dead agent cannot report itself dead, so something else has to notice the absence.
Evidence attached to the finish. The run has to hand over proof that the outcome exists: a file, a URL that must return 200, or an assertion. No evidence on a green finish is NO_EVIDENCE (case 3). Evidence that exists but is far outside the last seven runs in size or duration is DRIFT (case 4), measured with median absolute deviation so one strange run does not shift the baseline.
That is what "green does not equal done" means in practice: you decide up front what done looks like, and the run is only counted when it arrives.
Setting it up for Claude Code Routines and headless claude -p
Register the agent with its cadence and say that evidence is required:
rv agent nightly-digest --cadence 24h --cap-run-cost 2 --evidence
For a cron or any shell scheduler, wrap the command. The heartbeat and the evidence upload happen around your command, and the command is unchanged:
# crontab
15 6 * * * rv run nightly-digest --evidence-file /srv/digest/today.html -- \
claude -p "Build today's digest into /srv/digest/today.html" \
--output-format json --max-turns 40
For Routines or an interactive Claude Code session, install the plugin. It hooks SessionStart, PostToolUse and Stop, so every tool call is a heartbeat and the Stop hook closes the run with tokens and cost read from the transcript. Three environment variables configure it, and in a Routine you set them in the cloud environment:
RUNVOUCH_KEY=rv_live_... # your API key RUNVOUCH_AGENT=nightly-digest # which agent this session reports as RUNVOUCH_EVIDENCE=/srv/digest/today.html # a file path, an https:// URL, or cmd:
Evidence can be one of three things:
- A file path. The run is NO_EVIDENCE if the file is missing or was not modified during the run.
- A URL. Checked after the run; a non-200 response fails the check. Good for "the deploy is live" or "the page was regenerated".
- An assertion, such as a command that must exit 0. This is how I check the trading bot: the assertion queries the exchange and confirms the expected order state, not the bot's own log line saying it placed the order.
Alerts go to Telegram, Slack or a webhook. There is an MCP server too, so an agent can read its own run history and, for example, refuse to start if the previous run is still open.
What I would not do
I would not make the agent responsible for reporting its own failure. It cannot report a run that never started, and a hung run is by definition not reporting. The clock has to live outside.
I would not treat the evidence check as optional for "simple" jobs. Every one of my silent failures was on a job I considered too simple to need checking.
I would not let the watchdog become a dependency. rv run fails open: if RunVouch is unreachable, your command runs as normal and you lose one run of monitoring, not the run.
Free for three agents, $9 Solo, $29 Team, MIT self-host, EU hosting. Details at runvouch.com.
FAQ
My Routine shows green. Do I still need this?
Per the docs, green means the session "started and exited without an infrastructure error" and "does not mean the task in your prompt succeeded". Green covers case 1 partially and cases 2 to 4 not at all. The evidence check is what covers the rest.
What happens if the run legitimately has nothing to do?
Write that as evidence. A small file saying "no changes since 2026-08-24" is a valid outcome, and it keeps DRIFT from firing because the run's size is consistent. Silence and "nothing to do" should never look the same.
Does the plugin add latency or cost to a Claude Code run?
The hooks make small HTTP calls per tool use and at start and stop; they do not add tokens. Cost and token counts are read from the transcript the session already writes, not re-computed with extra model calls.
Try it: free for 3 agents · Docs: Claude Code · cron