I ran Claude Code unattended, all night
August 9, 2026
I keep a running list of small, well-defined tasks I never get to: a missing health-check endpoint, a flaky test, a config file that could use a default. None of them are hard. All of them lose every day to whatever is actually on fire. So I built auto-claude, a bash loop that picks tasks off a queue, runs Claude Code against each one in isolation, and only keeps the result if a command I chose says it passed.
The bottleneck isn't the model
The premise I started from: if the work is specified precisely enough, and something other than my judgment at 3 a.m. decides whether the result is acceptable, there's no reason a human needs to sit in the loop approving every step. That "something else" is a verification command — a test suite, a build, a lint pass, whatever proves the task worked. It either exits 0 or it doesn't. No vibes.
That reframes the actual bottleneck. It's not model capability, it's task specification. A vague task produces a vague result regardless of how good the model is. A task with a closed objective, verifiable acceptance criteria, and pointers to the right files is bounded by the model's competence, not by how many hours I spend babysitting the terminal.
How the loop works
1queue/todo/*.md --> for each task:2 git worktree from BASE_BRANCH (my checkout stays untouched)3 claude -p <- prompts/build.md + the task4 VERIFY_CMD5 |- pass -> commit on branch auto/<task> [push] [PR]6 |- fail -> claude -p --resume <- prompts/fix.md7 (bounded by MAX_FIXES)8 --> queue/done/ or queue/failed/9 --> logs/report-<timestamp>.md
One task at a time, each in its own git worktree so a bad run can't touch my actual checkout. If verification fails, Claude gets one more shot with a fix prompt, bounded by MAX_FIXES so a stubborn task doesn't burn the whole night. In the morning I read one report file: pass or fail per task, branch, diffstat, duration, cost.
The guardrails are the point
The loop itself is maybe 300 lines of bash. Nothing clever. Where the actual engineering went is the list of things that go wrong when nobody's watching, and what stops each one:
- A run dying on a usage limit doesn't lose the task — it sleeps and resumes.
- A hung verification command doesn't freeze the night — it gets killed after a timeout.
- Two runs starting at once (a cron overrun plus me starting one manually) — a lock file refuses the second and recovers automatically if the first died mid-run.
- Nothing gets marked done without
VERIFY_CMDactually passing, and the prompts explicitly forbid deleting or skipping tests to get there. MAX_BUDGET_USDcaps spend per invocation; the report totals the real cost.- Everything defaults to not pushing and not opening a PR. The loop stops at a local branch until I've read enough mornings of reports to trust it.
None of this makes the model more capable. It makes an incapable or confused run fail loudly and cheaply instead of quietly doing something wrong on a shared branch.
What I'm not pretending
PERMISSION_FLAGS defaults to --dangerously-skip-permissions, because that's what unattended operation actually requires — Claude runs shell commands, edits files, and reaches the network without asking. The worktree isolates my source tree, not my machine or my credentials. Anything inside that worktree can see whatever SSH keys, cloud sessions, and tokens my shell can see. I'm not describing that as a sandbox, because it isn't one. If I were pointing this at something I actually cared about protecting, I'd run it inside a container with only the credentials that specific task needs, and I'd replace the permission flag with an explicit allowlist instead of skipping the check entirely.
Right now I run it against throwaway repos and side projects, PUSH_BRANCH and OPEN_PR both off, and I read every branch before it goes anywhere. That's not a limitation of the tool so much as the appropriate amount of trust for a script that executes AI-generated shell commands while I'm asleep.
What actually changed
Writing the task file is now the part that takes effort, which is the correct place for effort to go. "Add GET /health returning {"status":"ok"}" survives an unattended run. "Improve observability" does not — there's no verification command that can judge that, so it either loops forever trying to guess what I meant or passes something I didn't ask for. Once I started writing tasks like the first kind, the backlog of small annoying things actually started shrinking overnight instead of accumulating.
Repo: auto-claude on GitHub