Runs a task in headless Claude Code (-p) autonomously and resiliently to quota. It's a Ralph loop (run the agent in a loop until the task is done) with built-in usage-limit handling: when the subscription hits its quota, the script sleeps and resumes the same session once the window resets. Multi-account aware on the same machine via CLAUDE_CONFIG_DIR.
claude-loop --account work \
--path ~/Workspaces/my-project \
--prompt "Implement the recorder mp4 export with tests." \
--until 04:05Claude Code has no automatic resume after a quota reset. In interactive mode, hitting the limit leaves the process alive but blocked at the REPL — and two sessions writing the same transcript corrupt it (there is no file locking). In headless mode (-p) the process exits cleanly on quota, which makes it safe for an external loop to wait for the reset and resume with --continue. This script is that loop.
cd ~/Workspaces/ai-tools/claude-loop
./install.sh # creates symlink ~/.local/bin/claude-loop -> claude-loop.shUpgrades: git pull in the repo (the symlink points at the versioned file).
claude-loop --path <dir> --prompt "<work>" [options]
| Flag | Description |
|---|---|
-d, --path DIR |
Project directory the session runs in |
-p, --prompt TEXT |
Initial instruction (the real start of the work) |
--config-dir wins over --account; both are optional.
| Flag | Description |
|---|---|
-a, --account NAME |
Account name resolved from the accounts file (see Accounts) |
--config-dir DIR |
CLAUDE_CONFIG_DIR to use directly (skips the accounts file) |
| Flag | Description |
|---|---|
-s, --sleep DUR |
Duration of each wait cycle (default: 30m). Accepts the sleep format (30m, 1h, 1800) |
-u, --until HH:MM |
Sleep until this time (next occurrence) on the first quota hit; later cycles fall back to --sleep |
--until takes priority on the first wait (ideal when you know the reset time). If you're still in quota when it wakes, it falls into the --sleep cycle until it clears.
| Flag | Description |
|---|---|
-S, --session UUID |
Fixed session UUID for deterministic resume. The first call creates it with --session-id, later calls resume with --resume. Pass auto to have the script generate (and log) a UUID |
-N, --session-name S |
Session display name (shown in the /resume picker). Applied only on creation |
Name vs UUID: Claude Code identifies sessions by UUID; names (--name) are just display labels. In headless always use the UUID — an ambiguous --resume <name> opens the interactive picker, which hangs with nobody to answer. Without --session, the script uses --continue (most recent session in --path), which is more fragile when several sessions touched the same directory.
| Flag | Default | Description |
|---|---|---|
-r, --resume |
off | Treat the session as already existing from the first call (resume the most recent in --path, or the given --session). Use after an interactive brainstorm — exit that session first |
-l, --log-dir DIR |
~/.claude-loop-logs |
Log output directory |
-m, --max-iters N |
300 |
Iteration cap (safety brake) |
--marker TEXT |
<<<TASK_COMPLETE>>> |
Marker that signals completion |
--perm MODE |
bypass |
bypass = --dangerously-skip-permissions · accept = --permission-mode acceptEdits |
Each iteration:
- Runs
claude -pheadless on the right account. - Found the marker in the output? → exit success (exit 0).
- Detected quota? → sleep (
--untilon the first time, otherwise--sleep) and retry. - Neither done nor quota? → keep working (Ralph loop), short 10s pause.
- Reached
--max-iterswithout the marker → stop and ask for review (exit 2).
The completion marker is appended automatically to the prompt, instructing the model to write it only when the task is genuinely verified.
To resume exactly the brainstorm session, pin a UUID to it and pass the same one to the loop:
# 1) Generate a UUID and brainstorm in the interactive session with that id; then EXIT (/exit)
U=$(uuidgen)
claude --session-id "$U" # your usual alias + fixed id, in the project directory
# 2) Fire the loop resuming that same session (-r = already exists)
claude-loop -a work -d ~/Workspaces/my-project -r -S "$U" \
-p "Execute the plan we agreed on, starting at step 1." \
--until 04:05Without pinning a UUID, use just -r and the loop continues the most recent session in --path via --continue. Either way, exit the interactive session before firing the loop — it avoids transcript conflicts between the interactive and the headless process.
--perm bypass (default) skips prompts, but does not override deny rules or PreToolUse hooks — precedence is always deny → ask → allow in any mode. So protections like "never git commit" still hold, as long as they're implemented as a deny rule in settings.json or a hook. Confirm this before running unattended.
--perm accept is more restrictive (auto-approves edits only), but in headless it can block commands that would need approval (running tests, builds), since there's no one to answer the prompt.
Headless = no review by you while it runs. Check
git diffand the logs before accepting anything.
The account → CLAUDE_CONFIG_DIR mapping lives in the accounts file at ~/.config/ai-tools/claude-loop/accounts (override with CLAUDE_LOOP_ACCOUNTS_FILE). Each line is <name> = <CLAUDE_CONFIG_DIR path>; # lines and blanks are ignored, and ~/ expands to $HOME. See accounts.example for the format.
# Add an account (prompts for the dir if --config-dir is omitted)
claude-loop --add-account work --config-dir ~/path/to/work-config
# List configured accounts
claude-loop --list-accounts--config-dir DIR bypasses the file entirely and sets CLAUDE_CONFIG_DIR directly. When neither --account nor --config-dir is given, the current environment's CLAUDE_CONFIG_DIR (if any) is used.
Written to <log-dir>/<account>-<project>.full.log (cumulative) and .last (last iteration, used to detect state). The directory comes from --log-dir, else the CLAUDE_LOOP_LOG_DIR env var, else ~/.claude-loop-logs.
| Var | Default | Use |
|---|---|---|
CLAUDE_LOOP_ACCOUNTS_FILE |
~/.config/ai-tools/claude-loop/accounts |
Accounts file (name → CLAUDE_CONFIG_DIR) |
CLAUDE_LOOP_BIN |
~/.local/bin/claude |
Claude Code binary |
CLAUDE_LOOP_LOG_DIR |
~/.claude-loop-logs |
Log directory |
CLAUDE_LOOP_BIN_DIR |
~/.local/bin |
(install.sh) where to create the symlink |
- The quota reset time only appears as human-readable text; that's why waiting is by
--until/cycles, not timed to the exact reset second. - Completion depends on the model honoring the marker instruction — use a well-defined "done" criterion in the prompt.
- There is no native quota retry in Claude Code; this loop is the external mechanism.
- Bash, GNU
date(Linux) - Claude Code CLI installed and authenticated on each account
nvm(optional; auto-loaded if present)