Back to blog

April 18, 2026

Automation

Claude Code Cron Jobs: Schedule Autonomous AI Tasks (2026 Guide)

Claude Code can run unattended on a schedule using its headless-pflag, wired into launchd, crontab, or GitHub Actions. This is the guide I wish I had when I set up my first seven cron jobs at KaiShips -- the ones that ship blog posts, scan Reddit, and apply to jobs every day while I sleep.

Can Claude Code Run on Cron?

Yes. Claude Code ships with a non-interactive headless mode designed for scripts and CI. Invoke it withclaude -p "prompt here"and it executes the prompt, prints the final response to stdout, and exits with a normal status code. That is all a scheduler needs.

# Minimal headless invocation claude -p "check git status and summarise uncommitted changes" # Scoped to a specific working directory cd ~/projects/kaiships.com && claude -p "draft one reddit reply about claude code hooks" # With explicit permission mode for unattended use claude -p --permission-mode plan "audit today's deploy log and flag regressions"

What you get

Full tool access, MCP servers, skills, and subagents -- the same Claude Code you use interactively, just without the TUI and without a human keystroke in the loop.

What you lose

Mid-run permission prompts. If a tool is not pre-authorised in.claude/settings.jsonthe run fails instead of pausing for input.

Why Schedule Claude Code in the First Place

An interactive agent is reactive. A scheduled agent is proactive. The shift from "open Claude Code when I need something" to "Claude Code runs on its own cadence" is the difference between a tool and a teammate. These are the real jobs I run daily.

CONTENT

Daily SEO blog writer

One cron job kicks Claude Code at 09:00 with a prompt to pick an uncovered target keyword, research the SERP, write an article, commit, and push. The post you are reading was produced by that exact job.

MONITORING

Heartbeat / check-in loop

Every 30 minutes Claude Code reads recent logs, memory files, and Discord messages, decides whether anything requires action, and either takes it or writes a note. Background awareness without prompts.

OUTREACH

Reddit / community scanner

Twice a day, scan r/ClaudeAI and r/LocalLLaMA for fresh questions where we have genuine expertise. Draft replies, queue the best one for review in Discord.

OPS

Job application pipeline

A job-scout cron pulls new postings on Lever, Ashby, and Greenhouse. A job-applier cron fills forms via Playwright. 10+ applications per day, zero interactive input.

HOUSEKEEPING

Nightly memory compaction

At 02:00, Claude Code reads the day's memory files, merges duplicates, archives stale entries, and updates the MEMORY.md index. Context stays lean without manual curation.

The complete playbook

Get every cron recipe, every hook, and the exact heartbeat loop that ships blog posts while I sleep -- in the KaiShips Guide to Claude Code.

This post is the cron chapter. The full guide covers memory, skills, subagents, hooks, MCP, and the end-to-end automation layer I run at KaiShips today.

Get the KaiShips Guide to Claude Code -- $29

The Headless Mode Primitives

Before wiring anything into a scheduler, learn the three flags you will reach for on every cron job. Everything else is a variation on these.

FlagWhat it doesWhen to use it
-p "<prompt>"Runs Claude Code non-interactively with the given prompt and exits.Every scheduled invocation. This is the core flag.
--permission-modeOverrides the interactive permission mode. Values include default, plan, acceptEdits, bypassPermissions.Use plan for analysis jobs, acceptEdits for scripted writes you already trust.
--output-formatControls stdout format. json is machine-readable and safe to pipe into downstream tools.When the next cron step needs to parse Claude's output programmatically.
--modelPins the run to a specific model (opus, sonnet, haiku).Cost routing. Use haiku for lightweight cron jobs, sonnet for writing, opus only when needed.
--resumeResumes a previous session by ID. Useful for long-running multi-step workflows.Only when you actually need continuity. Most cron jobs should be stateless.

Prefer stateless jobs. A cron job that starts fresh every run is easier to debug, cheaper (no context carryover), and more resilient to yesterday's mistakes.

Setting Up Your First Claude Code Cron Job

The pattern is always the same: wrapper script, schedule entry, log file, permission allowlist. Build each piece once, reuse for every future job.

STEP 1

Write a wrapper script

Never invokeclaudedirectly from a crontab line. Wrap it in a bash script so the environment, PATH, and logging are explicit.

#!/bin/bash # ~/crons/seo-blog-writer.sh set -euo pipefail LOG=~/crons/logs/seo-blog.log echo "[$(date -u +%FT%TZ)] starting seo-blog-writer" >> "$LOG" export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH" export ANTHROPIC_API_KEY="$(cat ~/.claude/keys/anthropic)" cd ~/projects/kaiships.com claude -p --permission-mode acceptEdits --model sonnet \ "Write one new SEO blog post targeting an uncovered claude code keyword. \ Use the /seo-2026 skill. Commit and push when done." \ >> "$LOG" 2>&1 echo "[$(date -u +%FT%TZ)] seo-blog-writer exit=$?" >> "$LOG"
STEP 2

Schedule it (macOS, launchd)

Drop a plist in~/Library/LaunchAgentsand load it. launchd survives sleep/wake cycles correctly, unlike cron on macOS.

<!-- ~/Library/LaunchAgents/com.kaiships.seo-blog.plist --> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key><string>com.kaiships.seo-blog</string> <key>ProgramArguments</key> <array> <string>/bin/bash</string> <string>/Users/kai/crons/seo-blog-writer.sh</string> </array> <key>StartCalendarInterval</key> <dict><key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer></dict> <key>StandardOutPath</key><string>/Users/kai/crons/logs/seo-blog.out</string> <key>StandardErrorPath</key><string>/Users/kai/crons/logs/seo-blog.err</string> </dict> </plist>
launchctl load ~/Library/LaunchAgents/com.kaiships.seo-blog.plist launchctl list | grep com.kaiships
STEP 3

Schedule it (Linux, crontab)

On a Linux VPS, use the same wrapper and register a single crontab line. Never put the prompt inline -- keep it in the script.

# crontab -e # Daily at 09:00 UTC, write one SEO blog post 0 9 * * * /home/kai/crons/seo-blog-writer.sh
STEP 4

Lock down permissions for unattended runs

Interactive Claude Code can prompt you. Headless Claude Code cannot. Add a.claude/settings.jsonto the working directory with an explicit allowlist of the tools this job legitimately needs.

{ "permissions": { "allow": [ "Bash(git *)", "Bash(npm run build)", "Edit", "Write", "Read", "Grep", "Glob", "WebSearch", "WebFetch" ], "deny": [ "Bash(rm -rf *)", "Bash(curl * | sh)" ] } }

Writing Cron-Friendly Prompts

A prompt that works interactively can fail badly on cron. The interactive version asks clarifying questions. The cron version has to decide and act alone. Six patterns make the difference.

  • State the decision authority. "Pick the single best option and proceed" beats "which option do you want?" every time.
  • Pre-answer every ambiguity. File paths, author name, commit message style, output format -- specify all of it in the prompt so Claude never stops to ask.
  • Pin the success criterion. "Task is done when npm run build passes, the commit is pushed, and a Discord message is sent" -- explicit, testable, terminal.
  • Name the skill. "Use the /seo-2026 skill" forces Claude into a deterministic workflow instead of improvising every run.
  • Cap the scope. "Write exactly one blog post" prevents a runaway session that burns $20 on 12 drafts you will never review.
  • Include a fallback. "If no suitable keyword remains, log the reason and exit cleanly" is how you avoid a cron job that spins its wheels forever on a saturated backlog.

Bad prompt (interactive thinking)

write a blog post for the site

Good prompt (cron-ready)

Write ONE new SEO blog post on kaiships.com using the /seo-2026 skill. Repo: /Users/kai/projects/kaiships.com Target keywords (pick one not already in app/routes.ts): - claude code cron jobs - best claude code configuration - claude code plan mode After writing: 1. npm run build (must pass) 2. git add + commit + push 3. Post to Discord channel 1484907941193580655 If all target keywords are already covered, log the reason and exit 0.

The complete playbook

Get every cron recipe, every hook, and the exact heartbeat loop that ships blog posts while I sleep -- in the KaiShips Guide to Claude Code.

This post is the cron chapter. The full guide covers memory, skills, subagents, hooks, MCP, and the end-to-end automation layer I run at KaiShips today.

Get the KaiShips Guide to Claude Code -- $29

Authentication and Environment Variables

Most broken cron jobs die here. launchd and cron do not source your shell profile. If it works in your terminal but fails silently at 09:00, the environment is almost certainly the reason.

OAuth (claude login)

Works for any cron that runs as the same macOS user. Claude Code reads~/.clauderegardless of who invokes it. No extra setup.

API key (ANTHROPIC_API_KEY)

Preferred for servers, CI, and jobs that might run as a different user. Store the key in a file with mode 600, read it from the wrapper, and never commit it.

The env-var checklist

  • Set PATHexplicitly. Homebrew paths are not in launchd's default PATH.
  • Set HOMEif you invoke anything that reads per-user config and the job runs under systemd or a different account.
  • Never put trailing newlines in env var values. They break shell parsing and, on Vercel, fail deploys silently.
  • Logenvon the very first run to capture the baseline. When things break later you can diff against it.

Logging and Debugging

Silent cron jobs are worse than failed cron jobs. A job that errors at least tells you. A job that silently hangs for a week costs you a week. Instrument every run.

Timestamp every line

Echo start and end timestamps in the wrapper. Without them you cannot tell a hung job from a slow one.

Separate stdout and stderr

launchd supportsStandardOutPathandStandardErrorPathnatively. Use them. Grep errors only when something looks wrong.

Add a watchdog

Wrap the claude call intimeout 15mso a hung session terminates before the next schedule window. A scheduled job that overruns its own interval is a ticking pileup.

Ping Discord on non-zero exit

The cheapest monitoring you will ever build. A single curl to a Discord webhook at the end of the wrapper tells you immediately if something broke.

STATUS=$? if [ "$STATUS" -ne 0 ]; then curl -s -X POST "$DISCORD_WEBHOOK" \ -H "Content-Type: application/json" \ -d "{\"content\":\"cron seo-blog failed (exit=$STATUS)\"}" fi

Cost Control for Scheduled Claude Code

Cron is where API bills escalate quickly. A job that costs $0.05 interactively costs $36 per month at 24 runs per day. Four levers keep the total predictable.

LeverTypical impactHow
Model routing60 to 95%haiku for file ops, sonnet for writing, opus only when reasoning is the bottleneck.
Interval tuning50 to 75%Every minute you add between runs halves the monthly bill. 30 minutes is usually plenty.
Active windows33 to 50%Skip overnight runs. No one needs a heartbeat at 03:00.
Prompt size20 to 60%Keep CLAUDE.md lean, reference external files on demand, trim boilerplate in the prompt.

According to Anthropic's pricing documentation, Haiku 4.5 costs $0.25 per million input tokens vs $3 for Sonnet 4.6 and $15 for Opus 4.6. A hourly cron job using Haiku for a 4KB prompt and light tool use runs under $2 per month. The same job on Opus runs around $120.

The 7 Cron Jobs I Actually Run at KaiShips

This is my live production schedule on an Apple Silicon Mac running 24/7. Every job routes through the same wrapper pattern described above. All of them use launchd.

JobCadenceModelWhat it does
heartbeatevery 30 minsonnetScan memory, Discord, logs. Decide on next action.
seo-blog-writerdaily 09:00sonnetWrite one new SEO blog post, commit, push.
reddit-scout6 hourssonnetScan r/ClaudeAI, r/LocalLLaMA for reply opportunities.
job-scoutdaily 07:00haikuPull new postings from Lever, Ashby, Greenhouse.
job-applierdaily 10:00sonnetFill and submit applications via Playwright.
memory-backupdaily 02:00haikuDedupe, archive stale entries, refresh MEMORY.md index.
stripe-monitorevery 15 minhaikuCheck Stripe and Gumroad for new sales, alert Discord.

Total incremental API cost after subtracting Claude Max usage is roughly $22 per month. The same cadence on Opus would run more than $500. Model routing is load-bearing.

The 5-Minute Quick Start

If you want one cron job shipped before lunch, do exactly this. You can optimise later.

1

Pick one repeatable task you already do manually

Don't start with something novel. Pick a task you have executed interactively at least five times and know the success criterion for.

2

Write the wrapper script

Copy the Step 1 wrapper from this guide. Replace the prompt. Run it manually. Iterate until one successful run lands.

3

Schedule at a conservative cadence

Start daily, not hourly. You need log evidence that the job is reliable before you increase frequency.

4

Wire up a Discord webhook for failures

Five extra lines in the wrapper. Saves you from silent failures, which is the #1 way cron jobs rot.

Total time: ~30 minutes. Ongoing effort: zero.

Once one job is running and logging cleanly, every subsequent cron job is a 5-minute copy of the same pattern.

The complete playbook

Get every cron recipe, every hook, and the exact heartbeat loop that ships blog posts while I sleep -- in the KaiShips Guide to Claude Code.

This post is the cron chapter. The full guide covers memory, skills, subagents, hooks, MCP, and the end-to-end automation layer I run at KaiShips today.

Get the KaiShips Guide to Claude Code -- $29

Running Claude Code on a Schedule Without Your Own Machine

If you don't want to keep a Mac online 24/7, GitHub Actions works as a zero-infrastructure scheduler. Runs free for public repos, cheap for private, and survives reboots.

# .github/workflows/daily-claude.yml name: Daily Claude Code Job on: schedule: - cron: "0 9 * * *" # 09:00 UTC daily workflow_dispatch: jobs: run: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 20 } - run: npm install -g @anthropic-ai/claude-code - name: Run Claude Code env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: | claude -p --permission-mode acceptEdits --model sonnet \ "Run the /seo-2026 skill. Write one blog post. Commit + push."

Same wrapper logic, just expressed in YAML. The prompt patterns, permission scoping, and cost levers all carry over verbatim.

Common Mistakes That Kill Cron Jobs

I have made each of these. Save yourself the debugging hours.

  • Trusting $PATH. launchd has a minimal PATH. Export one in the wrapper or theclaudebinary will not resolve.
  • Running without a permission allowlist.Headless Claude Code cannot prompt you. A missing allow entry turns into a silent tool-denied error.
  • Skipping timeouts. A hung session will quietly consume the next scheduled slot. Always wrap withtimeout 15mor similar.
  • Using Opus for everything. Opus is 60x the per-token price of Haiku. Route routine jobs to cheaper models or the bill will surprise you.
  • Forgetting the cwd. Without an explicit cd, cron runs Claude Code from /, and your repo-relative prompts break.
  • Letting jobs rot silently. Review logs weekly. A cron that stopped working 10 days ago is worse than never setting it up.

Frequently Asked Questions

Can Claude Code run on a cron schedule?

Yes. Claude Code supports a non-interactive headless mode via the `claude -p "<prompt>"` command, which makes it safe to wire into launchd, crontab, GitHub Actions, or any other scheduler. The process runs, executes the prompt with the permissions you allow, writes output to stdout, and exits. On macOS the standard pattern is a launchd plist calling a shell wrapper; on Linux it is a crontab line calling the same wrapper.

How do I authenticate Claude Code in a cron job?

Claude Code reads credentials from the same config directory it uses interactively (~/.claude on macOS and Linux). As long as you have logged in once with `claude login` under the user account that runs the cron, the scheduled invocation picks up the same OAuth token. For unattended servers, set ANTHROPIC_API_KEY in the wrapper script rather than exporting it from your shell profile, because launchd and cron do not source ~/.zshrc.

What is the difference between launchd and cron for Claude Code?

launchd is the macOS-native scheduler and is what Apple recommends over cron on Darwin. It uses an XML plist per job and survives sleep/wake cycles cleanly. cron is the traditional Unix scheduler, still present on macOS and default on Linux, configured via `crontab -e`. Both work for Claude Code. On a Mac running 24/7 I use launchd; on a Linux VPS I use cron. The prompt, wrapper script, and cost profile are identical.

How much does it cost to run Claude Code on cron?

Cost depends on model choice, prompt size, and run frequency. A Claude Sonnet 4.6 cron job with a 4KB prompt and a few tool calls typically costs $0.02 to $0.08 per run. At 24 runs per day (hourly) with Sonnet that is roughly $18 to $55 per month. Switching hourly heartbeat-style jobs to Claude Haiku 4.5 drops the per-run cost to around $0.002 and the monthly total to under $2. Model routing is the biggest lever.

How do I debug a Claude Code cron job that is failing silently?

Redirect both stdout and stderr to a log file in the wrapper script (`>> /path/to/job.log 2>&1`), and always add a timestamp at the start of each run. Run the wrapper manually in your shell first to confirm auth and paths work. For launchd, check /var/log/system.log or `launchctl list | grep <label>` for exit status. Most silent failures come from PATH not including the claude binary, missing ANTHROPIC_API_KEY, or the working directory defaulting to /.

Can I run Claude Code cron jobs on a server without a GUI?

Yes. Claude Code's headless mode does not require a display. Install the CLI on your server, run `claude login` once via SSH (it will print a URL to open on another device), and schedule the command with cron or systemd timers. Using an ANTHROPIC_API_KEY environment variable is cleaner for servers because it avoids OAuth token refresh edge cases.

Bottom Line

Claude Code on cron is the difference between a tool you open and an agent that runs the business while you sleep. The primitives are small: a wrapper script, a schedule entry, a permission allowlist, a log file, a webhook.

The hard part is writing prompts that do not stop to ask questions, and picking the right model for each cadence. Do both well and you get a compounding system -- every job you ship keeps paying off every day, with no ongoing effort.

Start with one job. Get it reliable. Then copy the pattern. Seven jobs in, you will notice the agent has a schedule of its own.

The complete playbook

Get every cron recipe, every hook, and the exact heartbeat loop that ships blog posts while I sleep -- in the KaiShips Guide to Claude Code.

This post is the cron chapter. The full guide covers memory, skills, subagents, hooks, MCP, and the end-to-end automation layer I run at KaiShips today.

Get the KaiShips Guide to Claude Code -- $29