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.

claude-loop-runner runs the Claude Code CLI unattended, one task at a time: 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

Where does the memory live between calls?

The memory lives in two markdown files inside the target repository, never in the conversation. You write macro_plan.md by hand, once — scope, architecture, task list. The run writes current_state.md after every iteration. Tag a task [low], [medium] or [high] and the runner passes that straight to claude --effort. 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
- [high]   Decide the payload shape
- [medium] Add the route
- [low]    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

What happens in one iteration?

One iteration runs six steps in a fixed order, and each step gates the next: a wrong branch or an unresolved rebase stops the loop instead of guessing past it.

  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 will it refuse to do?

claude-loop-runner refuses four things outright, and none of them are configurable: force-push, hard reset, any branch but main, and an uncapped run. 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; the guardrails below replaced it.

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

What do you need to run it?

claude-loop-runner needs Node 18 or newer, a Claude Code CLI login, and a plan file — nothing else. Point the token pool at one account 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

Straight answers

Questions people actually ask

What is claude-loop-runner?
claude-loop-runner is a Node CLI that runs the Claude Code CLI unattended, in a loop, against a task list you keep in a file. It is free and open source, maintained by ROI Labs, and works against your existing Claude Code login rather than a service of its own.
Why doesn't the context window fill up?
Because no iteration reuses the previous conversation. Each task starts a fresh claude --print process that reads only two small markdown files, so token usage per iteration stays roughly flat whether the plan has three tasks or thirty. There is no --continue, no --resume, and nothing to clear.
Where does the run keep its memory between iterations?
In two markdown files inside the target repository. You write macro_plan.md once — scope, architecture, ordered tasks — and the run never rewrites it. The run rewrites current_state.md after every iteration, carrying status, the next effort level, and what was just finished.
What happens when an account hits its rate limit?
The runner detects the limit banner whether it arrives as a non-zero exit or as ordinary stdout with exit code 0, puts that account on cooldown, and retries the same iteration on the next token in the pool. If every account is cooling, the run sleeps until the earliest reset.
Can it run on a branch instead of main?
No. The runner refuses to start unless main is checked out in the target repository, and it pushes after every successful iteration. That is deliberate — the branch-and-worktree cushion was removed in July 2026. If the repository auto-deploys on push, every iteration reaches production unreviewed.
What stops it from running forever?
Three things, none of them optional. --max-iterations is required and has no unlimited default, so a badly specified task cannot burn a weekly quota overnight. The loop also stops the moment current_state.md reports status: done or status: blocked — blocked being an ending, not a failure.
What do you need to run it?
Node 18 or newer, a Claude Code CLI login, and a plan file. Set CLAUDE_CODE_OAUTH_TOKEN for one account or CLAUDE_CODE_OAUTH_TOKENS for a comma-separated pool, then run node src/runner.mjs <repo> --max-iterations 20. An optional local UI is bound to loopback only.