claude-loop-runner unattended state 2 files branch main only iteration cap mandatory ROI Labs

Claude Code, running while you sleep

The context never grows.

Every iteration is a fresh, non-interactive claude --print call. No --continue, no --resume, nothing to /clear.

Long-term memory lives in two files inside the target repo — the plan you wrote, and the state the run keeps rewriting. The conversation never has to remember anything, so it never has to be trimmed.

Same 12 tasks, two ways to run them iteration 0

One long session

claude --continue

context in window0%

claude-loop-runner

claude --print, once per task

context in window0%
Both do the same work. Only one of them still fits at task 12.  

Where the memory actually lives

Two files, in your repo

You write macro_plan.md by hand, once — scope, architecture, task list. The run writes current_state.md after every iteration. Tag a task [plan] and it gets --effort high; tag it [build] and it gets low or medium. That tag is how you decide, in advance, how much deliberation each step deserves.

macro_plan.mdyou write it
## Scope
Ship the export endpoint.

## Tasks
- [plan]  Decide the payload shape
- [build] Add the route
- [build] Cover it with a test

Read at the start of every iteration. Never rewritten by the run.

current_state.mdthe run writes it
status: in_progress
next_effort: medium

## Done
- Payload shape decided: NDJSON

## Next
- Add the route at /export

Rewritten every iteration. This is the whole handoff between calls.

One iteration

Six things, in this order

Each step gates the next, which is why the order is worth stating.

  1. Confirm main is checked out

    If the target repo is on any other branch, the run errors out instead of guessing.

  2. Read current_state.md

    Picks up status and next_effort. Seeded automatically on the first run.

  3. Pick an account that isn't cooling

    Calls claude --print with that account's token and the effort the task asked for.

  4. On a rate limit, rotate — don't spend the iteration

    The banner is detected whether it comes back as a non-zero exit or as ordinary stdout with exit 0. That account goes on cooldown and the same iteration retries on the next one. If every account is cooling, the run sleeps until the earliest reset.

  5. Rebase on origin/main, then push

    The same sync runs before each iteration too, so work never builds on a stale tree. A rebase conflict stops the loop for a human.

  6. Stop on done, on blocked, or at the cap

    blocked is a first-class ending, not a failure — it means a human needs to look.

Safety, baked in

What it will not do

Every commit lands on main and is pushed immediately — deliberate, since 2026-07. If the target repo auto-deploys on push, each iteration reaches production with no human review in between. There is no branch cushion. These are the guardrails that replaced it, and they are not configurable.

git push --force

Hard-blocked through --disallowedTools, whatever --permission-mode says.

git reset --hard

Same block. History the run didn't write is not the run's to discard.

any branch but main

Refuses to start, so it can't quietly push to whichever branch you happened to leave checked out.

--max-iterations

Required. There is no unlimited default, so a badly-specified task can't burn a weekly quota across every account overnight.

status: blocked

Stops the loop instead of forcing progress.

Review the scope in macro_plan.md before a run — not the diff after.

Run it

Node, a plan, and at least one account

Point the pool at one token or at several. Several is the whole reason the cooldown logic exists: when one account hits its limit, the run keeps going on the next instead of stopping for five hours.

# one account
export CLAUDE_CODE_OAUTH_TOKEN=...

# or a pool — the run rotates when one starts cooling
export CLAUDE_CODE_OAUTH_TOKENS=tok1,tok2,tok3

node src/runner.mjs "C:\path\to\target-repo" --max-iterations 20

# optional local UI, bound to 127.0.0.1 only
npm run ui   # → http://127.0.0.1:4517
Read the code on GitHub