~/infra · brief

Running Claude Code 24/7 for data loops

The goal: Claude-driven jobs that pull data on a schedule, around the clock, without a laptop open. A version of this already runs here — nine launchd jobs (wsb-tracker, dashboard-refresh, the ad reports, the scheduler). This brief explains that setup, its one weakness, and the three ways to make it truly always-on.

How scheduled Claude actually works

A 24/7 loop is two separate pieces: a scheduler that fires on a cadence, and a headless Claude run (claude -p "…") that does the work and exits — no UI, no chat, just input → action → output. The headless command is the engine.

claude -p "Pull yesterday's FB ad spend and append to spend-log.csv" \ --allowedTools "Bash,Read,Edit" # pipe data in, JSON out, parse with jq cat raw.json | claude -p "summarize anomalies" --output-format json | jq -r '.result'
-p / --print
Non-interactive: process one prompt, output, exit.
--allowedTools
Pre-approve exactly what it may do, so nothing hangs on a permission prompt.
--bare
Skip auto-loading hooks/skills/MCP/CLAUDE.md so every run is identical. Recommended for scripts; becoming the -p default.
--output-format json
Machine-readable result plus total_cost_usd per run.
--permission-mode
acceptEdits lets it write files without prompts for unattended runs.
Billing change. Starting June 15, 2026, claude -p / Agent SDK usage on subscription plans draws from a separate monthly Agent SDK credit pool, not your interactive limits. Budget for it if you scale up loops.

The decision: where does it run?

Three options. The current setup is A. The Mac Studio sits on a desk on AC power, which matters for the recommendation.

ApproachAlways-on?EffortCostBest when
A. The Mac (launchd) — currentOnly when Mac awake + onlinenone (built)$0 extraLocal files/scripts, occasional gaps OK
B. Anthropic Routines (cloud)Yes, trulylowplan usageSelf-contained tasks on repos/connectors
C. A VPS (Linux server)Yes, trulymedium~$5–10/moCustom 24/7 loops, full control, your secrets

A. Stay on the Mac — fix the one weakness

The nine launchd jobs are real always-on infrastructure with one gap: launchd only fires when the Mac is awake, online, and logged in. A sleeping Mac Studio = missed runs. Two fixes:

01
Keep it awake permanently
For a desktop on AC, sudo pmset -a disablesleep 1 stops it sleeping across reboots. Lighter touch: caffeinate -s prevents sleep only on AC power.
02
Catch missed runs
launchd's StartCalendarInterval runs a job at next wake if the scheduled time passed while asleep — unlike cron, which just skips. So if the Mac stays awake, you're covered. Confirm time-of-day plists use StartCalendarInterval, not a cron-style StartInterval.
For a Mac Studio that lives on a desk on AC power, Option A + pmset disablesleep 1 is genuinely enough for most loops. Don't move to a server if the Studio is always plugged in.

B. Anthropic Routines — cloud, zero infrastructure

Anthropic now runs scheduled Claude Code on their infrastructure. A routine = a saved prompt + repos + connectors, with triggers. Runs even with the laptop closed; nothing on your machine.

Scheduled
Hourly / nightly / weekly. Custom cron via /schedule update. 1-hour minimum interval — faster is rejected.
API
POST to a per-routine endpoint with a bearer token to trigger on demand.
GitHub
Run on PRs / releases.
Fit
Best for self-contained tasks living in a repo or using connectors. Less ideal for the current data loops, which depend on local scripts, local .env secrets, and writing into john-labs/. Use Routines for "review this repo nightly"; keep local/VPS for "run my Python scraper and commit results".

C. A VPS — your own always-on Linux box

A $5–10/mo Linux server (Hetzner, DigitalOcean) that's online 24/7 by definition — no sleep problem, independent of the Mac. The move if you want loops that never depend on the Studio being on, or to keep heavy/long jobs off the daily machine.

01
Provision + install
Spin up a small Ubuntu VPS, SSH in (add it to the Tailscale tailnet so it's reachable like any other device), then install Node and Claude Code: npm install -g @anthropic-ai/claude-code.
02
Authenticate with an API key
No browser on a server, so use an API key from console.anthropic.com. Store it with tight perms — not in .bashrc: echo 'ANTHROPIC_API_KEY=sk-ant-...' > /opt/loops/.env && chmod 600 /opt/loops/.env. Keys never expire and need no browser. Note: API-key auth means no Remote Control on that box (that feature needs claude.ai OAuth) — fine for headless loops.
03
Schedule with cron or systemd
cron doesn't load your shell profile, so source the env explicitly. systemd timers are sturdier (logging, retries, Persistent=true to catch missed runs) once you outgrow cron. For long-running or interactive debugging, use tmux so sessions survive SSH disconnects.
# crontab — hourly loop, env sourced explicitly 0 * * * * cd /opt/loops && set -a && . ./.env && set +a && \ claude --bare -p "pull hourly metrics and append to data.csv" \ --allowedTools "Bash,Read,Edit" >> /var/log/loop.log 2>&1

Hardening any unattended loop

Applies whether it runs on the Mac, Routines, or a VPS.

Least privilege
Scope --allowedTools to the minimum (e.g. Bash(curl *),Read,Edit). Avoid --dangerously-skip-permissions on anything that can touch live ad accounts. The Telegram bot uses it — acceptable there because of the single-user lock, but don't copy that into an unattended scheduled job.
Idempotency
Loops re-run; make them safe to run twice. wsb-tracker already does this (checks for existing rows before inserting) — match that pattern.
Logging + alerts
Write each run to a log and have failures ping you — Telegram bot or email via Resend, both already in use.
Cost guard
Log total_cost_usd from --output-format json per run so a runaway loop is visible. Remember the June 15 Agent SDK credit change.
Secrets
Files chmod 600, never in shell history or committed. Keep the existing discipline (keys in tools/*.yaml / .env).

Recommendation

The loops are local, file-based, and on AC power

Now: make the Studio reliable — sudo pmset -a disablesleep 1, verify time-of-day plists use StartCalendarInterval. That closes the only real gap for ~$0.

If you want a loop that runs even when the Studio is off (travel, reboots, isolation from the daily machine): stand up a small VPS and move the most critical loop (e.g. wsb-tracker) there.

For new self-contained tasks (nightly repo review): use Routines — no infra at all. Reach for the Agent SDK (Python/TS) only if a loop outgrows a shell one-liner and needs real control flow, structured outputs, or retries.