Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

claude-code-cron

Run Claude Code as an unattended scheduled worker — cron on Linux, Task Scheduler on Windows — with the guardrails that make that sane: a git-based cross-machine lock, failure alerts to your phone, and a prompt template with hard boundaries.

Extracted from a setup where headless Claude agents have been doing real daily work for months (inbox triage, KPI reporting, content publishing) across multiple machines with no human watching.

The pattern

cron / schtasks
   └─ runner.sh <task> <project-dir> <prompt-file>
        ├─ claim-task.sh   ← git-push-as-distributed-lock (optional, multi-machine)
        ├─ claude -p "Read <prompt-file> and follow it exactly, then stop."
        ├─ append everything to ~/<task>-auto.log
        └─ on failure: roll the lock back + push a ntfy.sh alert to your phone

The prompt file is the task definition. The runner never changes; you write one markdown file per task.

File Role
runner.sh Lock → run → log → alert. The whole lifecycle in one file.
claim-task.sh Distributed daily lock using git push atomicity. No server, no Redis — any shared private repo works.
runner.cmd Windows Task Scheduler equivalent (four set lines to edit).
prompts/example-task.md Prompt template: Context / Task / Hard boundaries / Reporting.
register-task.example.md One-line crontab and schtasks registration examples.

Hard-won design rules

Claim first, run second. Two instances once ran the same task simultaneously — the window between "read the state file" and "write it back" is a race. The fix: writing the lock is the race, and git push makes it atomic (only one non-fast-forward push wins). Better to occasionally skip a run than to ever run twice.

A failed run must release the lock. Claiming the day and then crashing means tomorrow's check says "already ran" — a silent one-cycle skip. runner.sh rolls the state file back to its previous value on any non-zero exit.

Never reset --hard a shared repo. Other agents may have uncommitted work in the same clone. The lock rollback touches exactly one path.

Silence is a failure mode. An expired token or an exhausted usage quota makes headless runs exit "successfully" having done nothing. Alert on failure (NTFY_TOPIC), and consider a heartbeat check on the output (did the expected commit/file actually appear?) rather than trusting exit codes.

Prompts need hard boundaries, not vibes. Every unattended prompt here carries an explicit stop-list — credentials, payments, irreversible actions, anything outside the repo — with the instruction to log-and-exit rather than improvise. See the example.

Windows traps. Batch files must stay pure ASCII (cmd.exe parses them in the ANSI codepage — a CJK comment can get executed; there's a hook for that). And anything spawning subprocesses on a CJK-locale Windows needs explicit UTF-8 (encoding="utf-8" in Python's subprocess), or reading gh/git output will crash.

A word on --dangerously-skip-permissions

Headless runs can't answer permission prompts, so the flag is unavoidable — which means your safety story moves to the layers around it: the prompt's hard boundaries, PreToolUse guardrail hooks, and running tasks in repos where the blast radius is acceptable. Don't point an unattended agent at anything you couldn't restore from git.

More Claude Code tooling

License

MIT

About

Run Claude Code unattended on a schedule (cron / Task Scheduler): runner script, git-push distributed lock for multi-machine setups, failure alerts via ntfy, and a hard-boundary prompt template. Battle-tested patterns from months of daily headless runs.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages