Loops Claude until every todo is done, committed, and checked off.
Restarts the coding session on a fresh context before rot.
You can spawn multiple instances to parallelize.
It's for practical Loop engineering.
Runs claude on a predefined prompt in a loop on the given todo list file. Makes it complete, commit, and check off each todo. The session stays interactive, so you can add prompts and make choices as it runs. Launches claude in auto permission mode by default, and restarts it on a fresh context before rot sets in.
- What it does
- Quickstart
- Install
- Todo file format
- Context Rot
- Loop engineering
- Usage
- Cleanup
- Tests
- Security
- Contributing
- Commercial Support
- Guides Claude to zero a todo list unattended — one task at a time until all are completed, committed, and checkmarked.
- Beats context rot — session Stop hook SIGTERMs
claudeonce its context total crosses a threshold (see Context rot) and restarts clean. Fresh context, no quality decay. - One task per session —
claudeexits once it has zeroed a single todo and the script restarts it, so every task runs on a context isolated from the task before it, which cuts token spend (~20% on a working instance). An instance that has nothing to claim waits in the shell without launchingclaudeat all, spending nothing. - Parallel by default — run many instances at once; they coordinate via git worktrees, each claiming todos the others haven't taken.
- Safe merges — cross-instance merge-back serialized through
flock; no races, no corrupted base. - Crash resilient — if
claudecrashes or is killed mid-task, its half-done work isn't lost. The next instance to come by — a peer switching to its next task, or the same loop restarted — reclaims the branch, finishes it, and merges it back. Work only counts as done once it lands on the base branch and its box is checked there. - Named sessions — every
claudesession is named(<instance id>) <nick> · <activity>, shown in the prompt box, the/resumepicker, and the terminal title. The nickname is a short word no live peer holds, so you can say "kill moss" instead of reading eight hex characters. Both id and nick head the instance's report, and survive context restarts. - Ctrl+C window — 5s pause between runs to stop cleanly.
sudo curl -fsSL https://raw.githubusercontent.com/IvanRublev/claudezero/refs/heads/master/claudezero.sh -o /usr/local/bin/claudezero
sudo chmod +x /usr/local/bin/claudezero
Change to your repo root with a todo-list file, and make sure the working tree is in a clean state (commit or stash any changes) and the todo file itself is committed on that branch.
Then run claudezero pointing to your todo-list:
claudezero todo.md
You can run that command in multiple parallel terminals to work through the todos faster.
⚠️ ClaudeZero runsclaudeunattended with permissions auto-approved and commits on its own to the branch you launch it on. Only ever point it at a todo file you wrote or reviewed, on a branch with a clean, committed tree — git is your only undo.
Representative zero-mode run (agent output between the markers elided):
$ claudezero todo.md
❄ ClaudeZero 0.0.15
zero mode · base master · fork → implement → commit → merge
… claude works a task: forks a worktree, implements, commits, merges, ticks its box …
❄ claude exited with code 0 after 1 runs · restarting in 5s · press Ctrl+C to stop
… fresh context, next task …
❄ execution stats (instance a1b2c3d4 · moss)
Todos: 12m 30s · 5 completed
ClaudeZero run loop: 48m 15s
Tokens: 5.8M Total
in 2.1k · out 84.3k · cache write 312k · cache read 5.4M
-----------------------------------------------
❄ TOTAL (3 instances)
Todos: 41m 12s · 14 completed
Tokens: 17.4M Total
in 6.3k · out 251.9k · cache write 903k · cache read 16.2M
❄ ClaudeZero surveys the frozen field, and is proud.The TOTAL block sums every instance of the run on this base branch — a crashed peer counts too, since its merged work did land. It prints on solo runs as well, with a singular heading. ClaudeZero run loop stays per-instance: parallel wall times overlap, so their sum is not a duration anything took.
Supported on macOS and Linux (the script is bash-3.2-safe, so stock macOS bash works).
sudo curl -fsSL https://raw.githubusercontent.com/IvanRublev/claudezero/refs/heads/master/claudezero.sh -o /usr/local/bin/claudezero
sudo chmod +x /usr/local/bin/claudezerobrew install IvanRublev/tap/claudezerobash, git, claude CLI, plus:
- flock — merge/worktree locking. Must be runnable, not just present.
brew install flock # macOS; Linux ships it in util-linux
This prerequisite is guard-checked at startup; the script exits with a clear message if it is missing.
Transcript-schema contract. ClaudeZero couples to one thing in Claude Code internals: the session transcript's message.usage schema. ClaudeZero's own Stop hook records each session's transcript_path (a field of the hook payload), and reads it twice, for two different readers. The context-rot guard (below) reads the newest usage record on every turn to decide whether to restart. The token report, after claude exits, reads the whole transcript and sums the message.usage fields of every assistant line: input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens — the four categories Anthropic bills separately. One API request is written as several transcript lines, one per content block, each repeating the same usage object verbatim, so the token report dedupes by the line's requestId — a rule that belongs to the token report alone, since the context-rot guard keeps only the latest record regardless of request. Both readers take only the first (parent) match of each field name on a line: usage.iterations[] repeats all four names one level down, and usage.cache_creation carries the ephemeral_5m/ephemeral_1h leaves that already sum into the parent. Transcripts are only ever read, and no schema change can fail a run — any parse miss on the token report prints Tokens: n/a, and any parse miss on the context-rot guard leaves the session running; either way the run continues.
Two limits: subagent tokens are invisible — a session that used the Agent tool writes no isSidechain usage lines, so anything the task prompt spawns is missing from the totals, and the size of the under-count is not measurable from inside; and the figures are per instance run, for this repo only, unlike whole-machine tools such as ccusage, whose denominator is every Claude Code session on the box.
GitHub-style Markdown checkboxes, one task per line. Each line carries a unique id as the first whitespace-delimited token right after the checkbox — it names the task's branch and worktree:
- [ ] SMTH-855 Add retry logic to the sync endpoint
- [ ] 7 Extract validation into its own module
- [ ] 7.a Cover validation with unit tests
- [x] SMTH-140 Set up CI pipeline
```
- [ ] EXAMPLE-1 An illustrative example, not a task
```[ ] = still to do, [x] = done (skipped). Before zeroing, the LLM validates the whole file: a task missing an id, or a duplicate id, stops the loop with a report. Checkboxes inside fenced code blocks (```) are ignored.
The session Stop hook computes its own restart signal. Models whose plain id means a 1M window restart at 200.000 tokens; everything else restarts at 160.000, 80% of an assumed 200k window. Matching is first-match-wins against the model id, falling to the 160.000 default when nothing matches.
Why a threshold below the context window limit at all: context rot. A long session accumulates tool output, dead ends and superseded reasoning that stay in the window and compete for attention, so quality decays well before the window fills — ClaudeZero restarts earlier. Which is possible due to amnesia by design, durable external state carrying what mattered forward (see Closing the loop). Restarting early is also the cheaper direction, since every turn re-sends the whole context; without a restart the re-orientation to another task costs tokens.
The two numbers rest on different evidence:
- 200.000 for a 1M window, anchored on Opus 4.8, the only high-confidence figure. Its system card §8.9 reports GraphWalks BFS 85.9 @256k → 68.1 @1M and Parents 99.3 → 83.3, its harness compacts at 200k, and CodeRabbit independently sees it "degrade visibly once context crosses 200k"
- Opus 4.6 and Sonnet 4.6 bracket the same knee on MRCR v2
- Opus 5 and Sonnet 5 publish no depth-resolved eval, yet still compact at 200k, so "holds throughout 1M" is a claim with nothing measuring it
- Fable 5 and Mythos 5 are absent from the evidence entirely — one measured curve, four families inheriting it
- 160.000 for the 200k default — 80% of the assumed window, and inference, not measurement: no 200k model publishes a long-context eval. Haiku 4.5's system card only notes it "frequently encounter[s] physical context-window limits", putting its knee nearer 80–100k — so 160000 is the permissive end, and Haiku 4.5 the standing candidate for its own row.
The model-threshold table is defined as CONTEXT_THRESHOLDS in the claudezero script.
Loop engineering shapes an agent's iteration cycle so it gets better across turns, not just runs once. It is the outermost of three nested levels — each one only works because the one under it holds:
- Spec — what to build. Expected outputs, constraints, acceptance criteria, done-conditions. The contract everything downstream enforces. Without it the layers above have nothing to check against. Here: the todo file, one task per line, each referencing a separate issue file with the details of the specification.
- Harness — how to keep the agent on the spec, in two directions. Feedforward guides steer before it acts (
CLAUDE.md, conventions, templates); feedback sensors catch after (tests, linters, type checks, review). Feedback alone repeats the same mistakes; feedforward alone never proves it worked. Here: the per-iteration algorithm below, plus whatever guides and checks your repo already has. - Loop — who does the prompting. The harness on a timer: self-triggering runs, isolated worktrees, subagents that verify and feed back. You stop prompting turn by turn and start designing the thing that prompts itself. Here: ClaudeZero with
--taskpromptinstruction on how to learn by prompting itself.
Levels 1 and 2 are yours; ClaudeZero supports level 3. Together they steer: when a mistake recurs, you don't only fix the code, you sharpen the spec and/or the harness, and the loop needs you less each pass due to the learning instruction.
Loop engineering has two halves:
- Mechanics — a durable loop over external state; disposable runs that restart before context rots.
- Learning — each turn carries a lesson forward, so the agent stops repeating mistakes.
ClaudeZero owns the mechanics and leaves the learning to you. It drills the form precisely — how to claim, zero, commit, and check off a todo without collision or rot. You bring the material — what this codebase's tasks should teach. Sensei drills the kata; you bring the fight.
The kata is a strict algorithm every instance runs, one task per claude session:
- Find & validate — collect tasks; a missing or duplicate id stops the loop.
- Judge independence by evidence — blocked only if the body quotably consumes an unchecked task's output; adjacency is not a dependency.
- Claim & re-check — one task per git worktree (branch = claim), then guard against a peer who already landed it.
- Implement & check off — scoped to that task, tick only its box, commit.
- Merge serially — on a conflict or over-check, self-heal once, else stop and hand off to the user rather than corrupt the base.
- End the session — the shell starts a fresh one for the next task, or waits without spending a token while peers hold the rest; when every box is checked, announce done and stop.
Learning rides inside this form. A restart is amnesiac by design — it throws away rotten context; only durable external state survives: git, the todo file, and CLAUDE.md, one of several steering channels Claude re-reads on every fresh run.
ClaudeZero never writes CLAUDE.md — the harness stays learning-agnostic, so you choose what's remembered.
Bake a reflection step into the task prompt; the lesson lands in a committed CLAUDE.md, survives the restart, and reaches peers after their next merge:
claudezero todo.md --taskprompt 'Implement the task following your setup.
When done, if you learned something that will help future tasks — a gotcha, a
project convention, a command that worked — append one concise bullet under a
"## Learnings" heading in the project CLAUDE.md, and include that edit
in the task commit.'Now "the leopard remembers what the last winter taught him."
The prompt above is the smallest version. Levers:
- Where lessons land — redirect to a dedicated
docs/learnings.md(imported intoCLAUDE.mdvia@docs/learnings.md) to keep the setup file lean. - When to reflect — gate on non-obvious tasks; most teach nothing, and unconditional reflection just grows noise.
- Keep it bounded — have Claude prune stale bullets, not only append; an ever-growing file is context rot itself. Under parallel zeroing, though, appending one distinct bullet conflicts far less than rewriting a shared block — prune when contention is low.
- Shape the lesson — fix a form ("symptom → cause → rule") so entries stay scannable, not diary prose.
- Aim past learnings — promote a lesson into a test or lint rule; it enforces itself where a bullet only advises.
Under parallel zeroing, every instance edits the same CLAUDE.md — expect merge churn, and lessons reach a peer only after its next merge-back.
The loop is yours to teach.
claudezero [path-to-todo.md] [-t, --taskprompt "how to process ONE todo" | -l, --loopprompt "literal loop prompt"]
claudezero -h-t and -l are mutually exclusive. Zero mode forks a worktree per task off the current branch.
-t task prompt — Zero mode processes each task with the given prompt. Defaults to implementing the task following Claude's own setup (CLAUDE.md).
-l loop prompt — skips zeroing entirely when set. Instead of walking a todo file, it loops claude on one literal prompt you supply, restarting on fresh context each time. Any todo-file-path you pass is ignored. This mode creates no worktree branches, so run just one instance at a time.
CLAUDEZERO_MAX_LOOPS — cap the number of context-reset iterations. Unset or 0 loops forever (until Ctrl+C); set >0 to exit the script after N restarts of claude.
CLAUDEZERO_MAX_LOOPS=3 claudezero todo.mdCLAUDEZERO_WATCHDOG — how long one claude may make no progress before it is killed. Default 15m; accepts plain seconds or an s/m/h suffix (900, 90s, 15m, 1h), and 0 disables it. A claude that stops making progress never exits, so without the timer the loop parks on it forever — no restart, no report, and nothing left to stop but the whole run. Progress is measured as claude's own cumulative CPU time, not wall clock: one that is thinking, streaming, or running tools burns CPU and keeps resetting the window however long the task takes, while one blocked on a dead socket burns none. When the timer fires it says so on its own console line, then SIGTERMs claude (the same signal the Stop hook uses, so the restart path is the usual one) and escalates to SIGKILL 10s later. The task worktree survives the kill: the next session reclaims it through the crash-recovery path.
CLAUDEZERO_WATCHDOG=45m claudezero todo.mdCLAUDEZERO_DEPENDENCY_WAIT — ceiling on the wait after a claude session walks the whole todo list and claims nothing because every unchecked task is dependency-blocked (step 2.a's independence judgment). Default 10m; same grammar as CLAUDEZERO_WATCHDOG (900, 90s, 15m, 1h), and 0 relaunches claude immediately every cycle, same as before this existed. Without it, a fully dependency-blocked list looks identical to a genuinely stuck one: a session launches, finds every remaining task blocked, ends its turn, RESTART_WAIT ticks down, another launches — same judgment, same nothing-claimed outcome, one claude session burned per cycle for zero possible progress. The session marks the block on its way out (.git/zero.sh no-claim-mark); the shell then waits, comparing a deterministic signature (the todo blob's SHA plus the sorted set of ids peers currently hold) instead of relaunching, and stops waiting the instant a peer merges the blocking task or its holder dies — or after this ceiling, whichever comes first, so a session always gets a chance to re-judge the list fresh.
CLAUDEZERO_DEPENDENCY_WAIT=20m claudezero todo.mdCLAUDEZERO_LINK — comma-separated top-level names symlinked from the repo root into every task worktree. Unset by default. A worktree is a checkout of tracked files only, so anything gitignored is absent there: if your todo lines point at spec files you keep in another git repository — issues/ISSUE-031.md holding the acceptance criteria for - [ ] ISSUE-031 … — the session never sees them and works from the one-line title alone. Listing the directory here links it in, so the criteria are readable and a tick lands in the real file rather than in a copy the worktree removal deletes. Each linked name is added to .git/info/exclude, so it stays out of the session's git add -A and out of this repository.
CLAUDEZERO_LINK=issues claudezero todo.mdclaude's TUI is written to fd 4, which stays on the terminal, so a pipe captures only ClaudeZero's own ❄ reports instead of every TUI redraw:
claudezero todo.md 2>&1 | { trap '' INT; tee ../run.log; }The trap keeps tee alive through Ctrl+C, so the final report and the TOTAL block land in the file. Without a redirect — or when stdin is not a terminal — the TUI falls back to stdout as before.
Normal exits tidy up after themselves. But a crash, a kill, or Ctrl+C mid-task can leave a claim branch and its worktree behind — by design, so the next run can reclaim and finish them. These leftovers are exactly what crash-recovery reattaches to, so only remove them once you've stopped every instance and are done zeroing the unchecked todos.
The easiest way is to let Claude Code walk the cleanup and confirm each removal with you. From the repo root:
claude "ClaudeZero left stray git worktrees and branches behind. Clean them up
interactively. Zero-mode worktrees are siblings of this repo named
../ts-<base>-task-<id>-<hex>; claim branches are named <base>-task-<id> (e.g.
master-task-7). Steps: (1) list them with 'git worktree list' and
'git branch --list \"*-task-*\"'; (2) for EACH one, show it to me and ask me to
confirm before deleting — never delete without my yes; (3) warn me before
deleting any branch whose commits are not merged into its base, since that
discards work; (4) after removals, run 'git worktree prune'. Do nothing
destructive without my explicit confirmation."To do it by hand:
git worktree list # find ../ts-<base>-task-<id>-<hex>
git worktree remove --force ../ts-master-task-7-a1b2c3d # each one you no longer want
git worktree prune # drop stale admin entries
git branch --list '*-task-*' # claim branches: <base>-task-<id>
git branch -D master-task-7 # each unmerged claim you're discardingOnly delete a branch whose work you've already merged or intend to throw away.
End-to-end tests live in TEST.md — written to be executed by an LLM agent. Point the coding agent at the file and it runs all scenarios autonomously: claude --permission-mode auto "execute TEST.md and return a report".
ClaudeZero runs claude unattended with auto-approved permissions and commits on its own. Read SECURITY.md for the trust boundaries and how to bound the blast radius before running. Report vulnerabilities privately per that file — not via public issues.
Fork, branch, run the tests, open a PR. Full steps in CONTRIBUTING.md.
Built and maintained by Ivan Rublev. Need integration help, Loop Engineering training for the team, or a one-time "office hours" consultation? See services.
Copyright © 2026 Ivan Rublev.
This project is licensed under the MIT license.
