Skip to content

About

A small, auditable bash loop that runs Claude Code unattended against a queue of tasks, gated by a verification command

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

4 Commits

Folders and files

Repository files navigation

auto-claude

A small, auditable loop that runs Claude Code unattended against a queue of well-specified tasks.

The premise is that the bottleneck in AI-assisted development is not the model, it is the human approving each step. Remove the human from the inner loop and the work is bounded by how well the tasks were specified rather than by how many hours you sit in front of the terminal. That only holds if two things are true: the work is defined precisely up front, and something other than your judgement at 3 a.m. decides whether the result is acceptable. Here that something is a verification command that either passes or does not.

This repository is deliberately small. It is a bash loop, two prompts, and a set of guardrails. The guardrails are the point.

How it works

queue/todo/*.md --> for each task:
                      git worktree from BASE_BRANCH   (your checkout is untouched)
                      claude -p  <- prompts/build.md + the task
                      VERIFY_CMD
                        |- pass -> commit on branch auto/<task> [push] [PR]
                        |- fail -> claude -p --resume <- prompts/fix.md
                                   (bounded by MAX_FIXES)
                    --> queue/done/ or queue/failed/
                    --> logs/report-<timestamp>.md

One task at a time, one worktree per task, one report per run. In the morning you read a single file.

Layout

auto-claude.sh        the loop
add-task.sh           task scaffolding helper
config.example.sh     configuration template (copy to config.sh)
prompts/build.md      system rules for the build session
prompts/fix.md        system rules for a fix round
queue/{todo,doing,done,failed}/
logs/                 reports, traces, prompts, verification output
test/run-tests.sh     suite, runs against a fake claude CLI

Requirements

  • claude (Claude Code CLI), authenticated
  • git, jq, bash
  • gh only if you enable OPEN_PR

Setup

chmod +x auto-claude.sh add-task.sh test/run-tests.sh
cp config.example.sh config.sh
$EDITOR config.sh          # REPO, BASE_BRANCH, VERIFY_CMD, SETUP_CMD
./add-task.sh "Add a health check endpoint"
./auto-claude.sh

config.sh holds machine-local paths and is gitignored; config.example.sh is the versioned template.

To run overnight without the machine sleeping and without holding the terminal:

caffeinate -is nohup ./auto-claude.sh > logs/nohup.log 2>&1 &

To run it on a schedule, 0 22 * * * cd ~/auto-claude && ./auto-claude.sh is enough. Note that cron runs with a minimal environment; make sure claude and jq are on the PATH it sees.

Writing tasks

This is where most of the outcome is decided, and it is the part that stays human. A task file is plain markdown in queue/todo/. Files are picked up in alphabetical order, so a numeric prefix gives you ordering.

A task that survives an unattended run has:

  • A closed objective. "Add GET /health returning {"status":"ok"}", not "improve observability".
  • Verifiable acceptance criteria. This is what the model uses to decide it is finished.
  • Context. Which files to look at, which pattern to follow, what not to touch.
  • The scope of an afternoon, not a sprint. Split larger work across several numbered files. Each task branches from BASE_BRANCH independently, so if task 02 depends on task 01 you must either say so in the text or merge them into one file.

queue/todo/00-example.md.sample is a worked example.

Guardrails

Risk Guardrail
Damage to your working tree Every task runs in its own git worktree on branch auto/<task>
Unreviewed code reaching the mainline The loop never commits or pushes to BASE_BRANCH; PUSH_BRANCH and OPEN_PR default to false
The run dying on a usage limit Limits are detected, the loop sleeps LIMIT_SLEEP and resumes the same task; nothing is lost
Two runs at once (a cron overrun plus a manual start) A lock under .lock/ refuses concurrent runs and recovers locks left by a dead process
A hung verification or setup command freezing the night VERIFY_TIMEOUT and SETUP_TIMEOUT kill the command after the configured seconds
A transient API outage (HTTP 500/502/503/504) Bounded retries (TRANSIENT_RETRIES every TRANSIENT_SLEEP seconds) before the session is given up
An unbounded loop MAX_HOURS stops new tasks outside the window; MAX_FIXES bounds fix rounds per task
Unbounded spend MAX_BUDGET_USD per invocation; the report totals actual cost
False completion Nothing is archived as done without VERIFY_CMD passing, and the prompts forbid deleting or skipping tests to get there
Work lost to an interrupted run Tasks left in queue/doing/ are requeued at the start of the next run
Wasted hours on broken credentials An HTTP 401 or 403 aborts the run immediately and requeues the task instead of retrying

Output

Each run writes to logs/:

  • report-<timestamp>.md - per task: PASS or FAIL, branch, diffstat, duration, fix rounds, and Claude's own handover summary, plus totals and cost.
  • run-<timestamp>.log - the loop's own trace.
  • verify-<task>.log - the last verification output for a failing task.
  • prompt-<task>-*.txt - exactly what was sent, for when a result surprises you.

Failed tasks keep their branch and worktree so you can inspect the work.

Tests

./test/run-tests.sh

The suite runs the loop end to end against a fake claude CLI on PATH, so it makes no API calls and costs nothing. It covers the happy path, base-branch isolation, fix rounds, the MAX_FIXES ceiling, usage-limit backoff, the authentication abort, requeueing of interrupted work, report accuracy, the run lock (concurrent and stale), transient 5xx retries, verification timeouts, permission-flag quoting, config validation, and task numbering. If shellcheck is installed, the suite also lints every script.

AUTO_CLAUDE_HOME relocates queue/ and logs/, and AUTO_CLAUDE_CONFIG points at an alternative config file. The tests rely on both.

Security considerations

Read this section before pointing the loop at anything you care about.

PERMISSION_FLAGS defaults to --dangerously-skip-permissions. That flag is what makes unattended operation possible, and it means Claude executes shell commands, edits files, and reaches the network without asking. The blast radius is bounded by the worktree and by the fact that nothing is pushed by default, not by the flag and not by the wording of the prompts. Prompt instructions are guidance, not a sandbox.

Practical consequences:

  • The process inherits your credentials. SSH keys, cloud CLI sessions, npm and Docker tokens are all reachable from inside the worktree. A worktree is isolation for your source tree, not for your machine or your accounts.
  • SETUP_CMD and VERIFY_CMD run arbitrary code, including whatever lifecycle scripts your package manager triggers. Untrusted dependencies get executed unattended.
  • Task files are prompts. Treat anything the loop reads, including files pulled in from the repository, as untrusted input capable of steering the model. Do not queue tasks you did not write.
  • Enabling PUSH_BRANCH or OPEN_PR makes the output visible to others. Keep them off until you have read a few mornings of reports and trust what you see.

Reasonable hardening, in rough order of value: start on a throwaway repository; run inside a container or VM with only the credentials the task needs; replace PERMISSION_FLAGS with an explicit allowlist and accept that the loop will occasionally stall waiting for a permission nobody will grant, for example:

PERMISSION_FLAGS=(--allowedTools "Read Edit Write Bash(npm *) Bash(git diff)")

set MAX_BUDGET_USD; and review every branch before merging.

Two mechanical protections back this up: the loop refuses a config.sh that is world-writable (it is sourced as code), and PERMISSION_FLAGS is passed to claude as an argument array, so a quoted allowlist reaches the CLI intact instead of being re-split by the shell. The legacy string form still works and is split honouring its quotes, with glob expansion disabled.

Extending

  • Parallelism. Worktrees already permit it. Run several instances against separate queues, mindful of your usage limits.
  • Notifications. Post the report summary to a webhook at the end of the run.
  • A review pass. Add a second claude -p call over the diff with a code review prompt, and let its verdict count toward verification.

Troubleshooting

  • "another run is active (pid N)". Two instances share the same AUTO_CLAUDE_HOME; let the first finish, or stop it. A lock whose owner died is detected and removed automatically on the next start.
  • Every call fails with HTTP 401 and the run aborts. An ANTHROPIC_API_KEY exported in your shell profile takes precedence over your claude.ai login. Unset it, or start the loop with env -u ANTHROPIC_API_KEY ./auto-claude.sh.
  • A task ends in queue/failed/. Its branch and worktree are intact. Start with logs/verify-<task>.log, then the prompt files.
  • Stale worktrees. git -C <repo> worktree prune, then delete the auto/* branches you no longer need.

Disclaimer

This software is provided as an example of an unattended workflow. It executes AI-generated code on your machine with your credentials and without human review at the moment of execution. You are responsible for everything it does: for the code it writes, for the commands it runs, for the cost it incurs, and for anything that reaches a shared branch. Review the output before it goes anywhere that matters. There is no warranty of any kind; see LICENSE.

License

MIT. See LICENSE.

About

A small, auditable bash loop that runs Claude Code unattended against a queue of tasks, gated by a verification command

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages