Docs › Claude Code: Routines, headless runs and hooks

Claude Code: Routines, headless runs and hooks

RunVouch watches Claude Code agents that run without you: Routines, claude -p in cron, desktop scheduled tasks. The plugin uses Claude Code hooks to report the start, every tool call, and the stop, including tokens and cost read from the transcript.

Install the plugin

/plugin marketplace add runvouch/claude-plugin
/plugin install runvouch

Or from a checkout: claude --plugin-dir ./integrations/claude-code-plugin. The plugin is a no-op in interactive sessions; it only reports when RUNVOUCH_AGENT is set.

Wire a Routine or cron job

# once: register the agent
rv agent nightly-report --cadence 24h --cap-run-cost 2 --evidence

# in the Routine / cron environment
export RUNVOUCH_KEY=rv_…
export RUNVOUCH_AGENT=nightly-report
export RUNVOUCH_EVIDENCE='{"report":{"type":"url","url":"https://example.com/reports/latest.html"}}'
claude -p "Generate tonight's report and publish it" --dangerously-skip-permissions

What gets reported: SessionStart → run start · PostToolUse → tool name + input hash + error flag (retry-storm and budget checks run on every call) · Stop → run end with tokens and cost summed from the transcript, plus your evidence · StopFailure → run failed, with the API error type (rate_limit, authentication_failed, billing_error, server_error and the rest) · SessionEnd → run failed, when the session was killed or exited before the turn finished.

Read Stop as "Claude stopped responding", not as "the job is done": the hook cannot see what you wanted. An expired session or a rate limit at 03:00 now arrives as FAILED with the reason, and everything beyond that is what RUNVOUCH_EVIDENCE is for. Without evidence a finished session is the only thing RunVouch can vouch for.

No client, just a URL

Every agent has its own ping URL. Nothing to install, no header, no JSON: anything that can call a URL can report to RunVouch, from a crontab line to an n8n node, a GitHub Actions step or a Docker HEALTHCHECK.

# one line in your crontab: report the exit code of the job
0 3 * * * /usr/local/bin/nightly.sh; curl -fsS -m 10 https://api.runvouch.com/ping/<token>/$?

# or bracket the job so RunVouch also sees how long it took
0 3 * * * curl -fsS https://api.runvouch.com/ping/<token>/start; /usr/local/bin/nightly.sh && curl -fsS https://api.runvouch.com/ping/<token>

The suffixes: nothing for success, /start to open a run, /fail for a failure, or the exit code itself (0 is success, anything else is a failure). GET, POST and HEAD all work. A POST body up to 100 kB is kept as the error excerpt on a failure. Ten pings per agent per minute.

The token is a write-only secret for that one agent: it can report runs and nothing else, so unlike your API key it can sit in a crontab or a shared workflow. rv agent NAME prints it, and it stays the same when you re-register the agent. A bare ping with no /start before it is a heartbeat: one run that begins and ends on the spot, which is all a dead man's switch needs.

Evidence: prove the task happened

Evidence turns "exit 0" into "done". Three forms:

If --evidence is set on the agent and a run ends "ok" without passing evidence, you get a NO_EVIDENCE alert.

Cost caps and retry storms

--cap-run-cost 2 and --cap-day-cost 10 alert the moment a run or a day crosses the line, and the agent is paused right there: the next rv run is refused and the command is not started. The run that crossed the line has already finished, so the cap stops the schedule, not the job in flight. Get it going again with rv agent NAME --resume. Only that explicit answer stops a command; if RunVouch is unreachable your job runs unmonitored, as everywhere else. Retry storms fire when the same tool is called with an identical input hash 8+ times in one run (configurable). Cost is computed from transcript usage with current Anthropic list prices; override with RUNVOUCH_COST / RUNVOUCH_TOKENS if you meter elsewhere. A job wrapped in rv run can also report what it spent by printing one line of its own, RUNVOUCH_COST=1.25 (and RUNVOUCH_TOKENS=), which works from any language without a client. rv run also puts RUNVOUCH_RUN_ID in the job's environment, so the job can add tool calls or evidence to the run it is already in.

Ask your agents about each other (MCP)

claude mcp add runvouch -e RUNVOUCH_KEY=rv_… -- python3 runvouch_mcp.py

Tools: runvouch_status, runvouch_alerts, runvouch_ack, runvouch_runs, runvouch_run_start, runvouch_run_end. A morning agent can refuse to build on last night's output if last night is UNPROVEN. See MCP docs.


Need a key? Get a free key · Stuck? contact