# How to Run Claude Code on a Schedule Without Sitting There > Canonical: https://www.yalc.ai/blog/run-claude-code-on-a-schedule/ The three built in options, the four traps that quietly kill laptop scheduling, and how to choose the runtime that actually survives the day it fails. To run Claude Code on a schedule, use one of three built in options. Use `/loop` for polling inside an open session, a Desktop scheduled task for local file work, or a cloud Routine on the Pro plan for anything that must fire with the laptop closed. Cron and launchd still work with four traps to plan for. ## Why Claude Code did not ship with a scheduler Claude Code started as an interactive coding partner. The first surface was a session you drove in a terminal, and anything that ran had a person watching. That model is fine when the job is "write this function," and it falls apart the moment the job is "check my deploy every morning, run the ICP enrichment when a signal lands, or babysit the release branch overnight." Those jobs need a fire trigger and a runtime. The terminal is neither. Anthropic has since added scheduling in three layers, walked through below. The operator's real question sits on top of those layers. Where does the trigger fire, and what does the runtime look like the day the job silently stops. If you have not yet placed Claude Code inside a broader GTM pattern, the [ways to use Claude Code for GTM](/blog/ways-to-use-claude-code-for-gtm/) walkthrough puts the scheduler question inside a real workflow instead of a toy example. ## The three ways Claude Code now schedules itself Anthropic's [scheduled tasks documentation](https://code.claude.com/docs/en/scheduled-tasks) lists three official surfaces. They look similar in a demo and behave very differently in production. **/loop, in an open session.** The `/loop` bundled skill runs a prompt repeatedly while the session stays open. You can give it an interval and a prompt (`/loop 5m check the deploy`), just a prompt (Claude picks the interval), or nothing (a built in maintenance prompt runs). Tasks are session scoped and expire in seven days. If you close the terminal, they stop. **Desktop scheduled tasks.** Available inside the Desktop app on macOS and Windows only. Each task fires a fresh session at the time you set, with full access to your local files and configured MCP servers. Minimum interval is one minute. Persistent across restarts. Requires your machine to be on and awake. **Cloud Routines.** These run in Anthropic's cloud on a schedule you define with a cron expression. Requires the Claude Pro plan at 20 dollars a month or higher, per Anthropic's own [pricing page](https://www.anthropic.com/pricing). Minimum interval is one hour. The runtime is a fresh clone of a connected repository, which means no access to files on your machine, no MCP configs from your desktop, and no state that survives across runs unless you write it back to git. That last point is the one every article about Cloud Routines glosses. If your job needs to read a CSV that lives on your laptop, or hit a private database inside your VPN, Routines is not the runtime. If your job needs to happen at 3 AM whether or not you fell asleep with the laptop open, Routines is the only built in that works. > Figure: Comparison of Desktop task, Cloud Routine, and always on box across four scheduling criteria For a wrapper, the [claude-code-scheduler plugin](https://github.com/jshchnz/claude-code-scheduler) drives launchd on macOS, crontab on Linux, and Task Scheduler on Windows, executing `claude -p` under the hood with logs, retry handling, and worktree isolation a raw cron line does not have. If you want the raw path, keep reading. ## Option 1, cron and launchd on your own machine The tightest feedback loop lives here. You already have cron on Linux and launchd on macOS, both are free, and `claude -p "your prompt"` runs Claude Code non interactively in headless mode. A concrete macOS launchd plist for a 9 AM weekday job looks like this. Save it to `~/Library/LaunchAgents/com.yalc.morning-run.plist`, then load it with `launchctl load ~/Library/LaunchAgents/com.yalc.morning-run.plist`. ```xml Labelcom.yalc.morning-run ProgramArguments /opt/homebrew/bin/claude -p Pull last night's signal feed and draft the morning shortlist. --dangerously-skip-permissions EnvironmentVariables PATH/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin ANTHROPIC_API_KEYsk-ant-... StartCalendarInterval Weekday1 Hour9 Minute3 StandardOutPath/tmp/morning-run.log StandardErrorPath/tmp/morning-run.err ``` On Linux, the crontab equivalent is one line: `3 9 * * 1-5 /usr/bin/claude -p "your prompt" --dangerously-skip-permissions >> /var/log/claude/morning-run.log 2>&1`. Both run outside a terminal and share the same four failure modes below. The minute is `3`, not `0`, on purpose. Anthropic's scheduler adds up to 30 minutes of jitter to any cron time that starts on the hour or half hour, so a raw `0 9 * * *` can quietly slide to 9:29 without warning ([Anthropic docs](https://code.claude.com/docs/en/scheduled-tasks)). A non round minute pins the fire time. ## The four traps that quietly kill laptop scheduling Every operator writing their first launchd plist hits one of these and blames the model. It is never the model. > Figure: Four step flow showing how a scheduled Claude Code run silently fails when environment and TTY assumptions break **Trap 1: environment variables missing.** When you run `claude` in your terminal, your shell has already loaded `.zshrc`, your API key, and every path your Homebrew install cares about. When launchd or cron fires, none of that is loaded. `ANTHROPIC_API_KEY` is empty, `PATH` does not include `/opt/homebrew/bin`, and the run either errors out or, worse, silently uses a different config. Fix: set `EnvironmentVariables` in the plist explicitly, or write a wrapper shell script that sources your profile before it runs `claude`. **Trap 2: no TTY, so interactive prompts hang.** Claude Code will ask for permission when it wants to touch a file, run a bash command, or hit an MCP tool that a policy has not preapproved. In an interactive session you type `y` and move on. In a scheduled run there is no terminal to type into, so the process waits forever. Fix: pass `--dangerously-skip-permissions` for jobs you actually want to run unattended, and lock down what the job can do at the settings layer instead. Skipping permissions is not the same as removing them; it just moves the gate from runtime to config. **Trap 3: logs go to `/dev/null` by default.** A cron entry with no redirect throws stdout into the void, and launchd without `StandardOutPath` does the same. When the job breaks in three weeks, you have nothing to read. Fix: always redirect stdout and stderr to a file, then rotate that file. On Linux, drop a simple `logrotate` config; on macOS, use `newsyslog.d`. A 200 MB log from a daily Claude Code run adds up fast because model reasoning traces are chatty. **Trap 4: overlapping runs.** The default assumption for cron is that the previous run finished. It usually did. Once you start running longer prompts, an "every 15 minutes" job that occasionally takes 20 minutes will fire the next one on top of itself, two sessions writing to the same files, one clobbering the other's git commits. Fix: wrap the call in `flock` on Linux (`flock -n /tmp/claude.lock claude -p ...`) or a `PID` file check in the launchd wrapper. Do not skip this. It is the trap that shows up last, when you have the most trust in the setup. If your team is building agents that need to be inspected and versioned, the same lessons apply from the other direction. The [building your own GTM agent](/blog/building-your-own-gtm-agent/) breakdown covers why you keep the prompt and the schedule in files you can grep, not in a UI that hides both. ## Option 2, a small always on box Once you accept that scheduling on a laptop means the laptop is never closed, the ergonomics get miserable fast. The fix is cheap. A used Mac Mini M1 pulls around eight watts idle and runs full Claude Code, native, with your own filesystem and your own MCP configs. Second hand pricing hovers around 300 dollars. A [Hetzner CX22 VPS](https://www.hetzner.com/cloud/) at roughly five euros a month, per Hetzner's public cloud pricing, gives you a Debian box that does not care about laptop lids. Both paths give you exactly what launchd or cron on a laptop gives you, without the "did the machine sleep at 2 AM" question. You still write the same crontab. You still get the same four traps. What you get on top is a runtime that behaves the same at 3 AM as it does at noon, which is the whole point of scheduling in the first place. Two decisions matter on this path. First, keep the box on your side of the network if the job reads private data, because a VPS in another country puts your GTM contacts on someone else's server. Second, use `git` as the state layer. Every run reads the current repo state and commits its output back to a branch, so you can inspect what any run did by reading a diff instead of a log. The same set of [Claude Code skills](/blog/claude-code-skills/) you run on a laptop moves one to one to a Pi, a Mini, or a Debian VPS. The cost math is worth stating plainly. A Mac Mini plus a year of electricity comes in under 400 dollars. Two years of a Team seat on Anthropic's plans, at 30 dollars per user per month per the [Anthropic pricing page](https://www.anthropic.com/pricing), is 720 dollars. If the job does not need a fresh clone, an always on box is the cheapest reliable runtime you can buy. ## Option 3, a managed agent runtime The middle option, which the ranking articles ignore, is a container runtime that gives you a scheduler, a filesystem, and persistent volumes without pretending it is your laptop. Modal, [Fly Machines](https://fly.io/docs/machines/), and Railway all fit. You define a container that has Claude Code installed, your API key, and whatever MCP servers your job needs. You schedule the container. You get logs, retries, and monitoring for free. The important property is that these runtimes give you real disk between runs. Anthropic's Cloud Routines start every run from a fresh clone of a repo, so anything you accumulated during the previous run is gone unless you committed it. Modal and Fly give you a mounted volume that survives. For any job that builds up state, an embedding index that grows, a signal history that compounds, a cache of enriched accounts, the volume matters more than the scheduler. Cost sits between the extremes. Modal charges by compute second and starts near zero for jobs that run for a minute a day. Fly's smallest always on VM runs around 3 to 5 dollars a month at typical use, per its [pricing page](https://fly.io/docs/about/pricing/). Neither replaces Routines for the "no server" case. Both beat Routines for anything that needs local file behavior without physical hardware. ## Choose by the cost of a silent stop The right frame for picking a scheduling surface is not "which one is easiest to set up" and not "which one is cheapest." It is what the job costs you the day it silently stops. A morning code review that no ships if it misses one day is a `/loop` job. Set it, forget it, no runtime spend. A weekly report that is embarrassing but not urgent if it misses is a Desktop scheduled task, because the machine will be on most weeks anyway. A signal driven outbound job that has to reach a fresh [PredictLeads](/tools/predictleads/) hiring signal within the same day it fires is not either of those. That job needs a runtime that stays up, an alert when a run fails, and a log you can actually read. The right home is either an always on box or a managed runtime, chosen by whether you need local files. This is where a GTM operating system pattern earns its keep. Yalc runs from markdown files on your own machine or your own box, so the scheduler is whatever runtime you already have, and the prompts, policies, and state are in git where you can diff a bad run against a good one. That is a different property from a black box that returns "task failed, retrying" in a dashboard you cannot inspect. For the wider framing on where an [agentic GTM operating system](/blog/agentic-gtm-operating-system/) sits inside a stack you already own, that piece walks through the middle mile that a scheduler feeds. For the sales side of the same argument, [Claude Code for sales](/blog/claude-code-for-sales/) covers what you put behind the trigger once the trigger is stable. Two rules make the pick almost mechanical. If the job needs to survive a closed lid and needs no local files, use Cloud Routines. If the job needs local files or needs to fire more than once an hour, use a Desktop task, a small always on box, or a managed runtime, and choose between the three by whether you care about maintaining hardware. Anything else is a session job that should live inside `/loop` while you are already in a session. ## Run one real GTM job this week Pick one job you would run by hand every morning if you had the time, and put it on a schedule this week. A good starting shape is a shortlist generator that reads yesterday's signal feed, ranks the ten most interesting accounts, and drafts a first line for each. If you have never wired one up, the [qualify leads skill](/skills/qualify-leads/) is the gate you drop in front of the draft so a bad account never makes it to the outbox. Start with `/loop` inside a session to prove the prompt behaves. Promote it to a Desktop scheduled task when the prompt is stable and the machine is usually on. Move it to Cloud Routines or a small always on box only when the cost of it silently stopping crosses the cost of running a real runtime. Do not skip the middle rungs. Every promoted job that fails in production started as a laptop cron job that worked for a week and then hit one of the four traps. The teams getting the most out of Claude Code on a schedule are not the ones with the fanciest runtime. They are the ones who wrote the prompt cleanly, kept the state in files they can read, and matched the runtime to what the job costs when it fails. The rest is plumbing. ## Frequently asked questions ### Do Claude Code scheduled tasks keep running if I close my laptop? Cloud Routines do, because they execute in Anthropic's cloud. Desktop scheduled tasks and any cron or launchd job on your own machine do not, because closing the lid puts most laptops to sleep and stops the timer. If you need a job to fire while the laptop is closed, either use Cloud Routines or run your scheduler on a machine that is always awake. ### What's the difference between /loop, Desktop scheduled tasks, and Cloud Routines? `/loop` runs inside an open session and dies when you exit or start a new conversation. Desktop scheduled tasks fire fresh sessions locally at any interval down to one minute, but need your machine on. Cloud Routines run without your machine, but require the Pro plan or higher, cannot go below a one hour interval, and start every run from a fresh repo clone. ### How much does it cost to run Claude Code on a schedule? `/loop` and Desktop scheduled tasks are included in whatever Claude Code plan you already have, so the additional cost is model usage. Cloud Routines require the Pro plan at 20 dollars a month or higher. Running your own scheduler on a small VPS or a used Mac Mini adds five to fifteen dollars a month plus the same model usage you would pay either way. ### Do I need Claude Pro to schedule Claude Code tasks? You need Claude Pro or higher to use Cloud Routines. You do not need it for `/loop`, Desktop scheduled tasks, or any cron or launchd job that shells out to `claude -p`. If your job can live on your own machine, you can schedule Claude Code on the free tier of the CLI and only pay for the model usage. ### Can I run Claude Code on a Raspberry Pi or a small VPS? Yes on both. Anything that runs Claude Code and can hold a cron entry works. A Pi 5 is enough for most scheduled prompts. A Hetzner or Fly VM at a few dollars a month is a cleaner path if you do not want physical hardware. The only real constraint is that the job cannot read files that live on a different machine, which is the same constraint Cloud Routines has. ### What happens if two scheduled runs overlap? The default is that they both run, at the same time, on the same working directory. That is how git conflicts, half written logs, and duplicate outbound sends start. Wrap the command in `flock` on Linux or a `PID` file check in your launchd wrapper so a new run refuses to start until the last one finishes. This is trap four from earlier, and it is the one that shows up quietly after everything else has been working for a month.