Vibe Code Textbook

Workflows · · 1,406 words · 6 min read

When should you stop a coding agent? Budgets, loops, and the signs

The budget flags harnesses ship, the loop signatures that predict a failed run, what the research says about early stopping, and a transcript analyser.

budgets loops claude code mini-swe-agent cost

What you get here: the hard budgets the harnesses expose and which one to set first, the three loop signatures that show up before a session fails, the published evidence that stopping early and restarting beats letting it run, and a script that reads a transcript and exits non-zero when a threshold is crossed. The script ran in this session on a constructed transcript; its output is below.

Budgets the harness will enforce for you

Every harness I read for this article has some form of hard stop, and they are more alike than their documentation suggests.

Claude Code exposes two flags in non-interactive mode. The CLI reference describes --max-turns as "Limit the number of agentic turns (print mode only). Exits with an error when the limit is reached. No limit by default," and --max-budget-usd as the "Maximum dollar amount to spend on API calls before stopping (print mode only)." The dollar cap counts subagents: "Spend from subagents counts toward the cap. Once spend reaches the cap, spawning another subagent fails with Budget limit reached, and Claude Code stops background subagents that are still running", with the note that this behaviour "require[s] Claude Code v2.1.217 or later."

The mini-swe-agent, the roughly hundred-line agent from the SWE-bench team, makes the same three limits visible in its source. In default.py at commit 04d809ceab9d, the query method checks termination before each model call: a step limit (0 < config.step_limit <= n_calls), a cost limit (0 < config.cost_limit <= cost), and a wall-clock limit (0 < config.wall_time_limit_seconds <= elapsed_seconds). There is a fourth stop that the larger harnesses hide: max_consecutive_format_errors, which ends the run when the model keeps producing output the harness cannot parse into an action. The run also ends on an explicit exit message. Those five conditions are the whole vocabulary of "stop": steps, money, time, malformed output, and done.

Omnigent, the meta-harness that wraps other agents, expresses the same thing as a policy rather than a flag. Its README at commit 83cc3e7113df shows a cost_budget policy with max_cost_usd: 5.00 and ask_thresholds_usd: [3.00], alongside a max_tool_calls_per_session policy with limit: 50. The ask threshold is the idea worth copying: a budget that pauses for a human at sixty percent is more useful than one that kills the run at a hundred.

Which to set first: the dollar cap, if your harness has one, because it is the only limit that scales with the model. A turn cap is cheap insurance for scripted runs. A wall-clock cap is what you want for CI, where a stuck run costs runner minutes as well as tokens.

The loop signatures

A hard budget stops the run. It does not tell you the run was going badly at turn twelve. For that you need the signatures.

The same tool call, again. An agent that runs python -m unittest test_tally five times with no edit in between is not testing; it is hoping. The best-practices page describes the human version of this as "Correcting over and over", where "Context is polluted with failed approaches," and its fix is blunt: "After two failed corrections, /clear and write a better initial prompt incorporating what you learned." The agent version has the same fix, and the same threshold. Two identical calls is a retry. Three is a loop.

Consecutive tool errors. Four tool results in a row marked as errors means the agent has stopped reading its own output. Each error costs the full context to re-send, so this is also where the money goes.

Context thrash. When a single file or command output is so large that the context refills immediately after being compacted, the harness gives up. The Claude Code architecture page states that in that case "Claude Code stops auto-compacting after a few attempts and shows an error instead of looping." In OpenHands the equivalent mechanism is the condenser, which its documentation describes as triggering when the event history exceeds max_size, default 120, while keeping the first keep_first events, default 4, verbatim. A session that has condensed several times is a session whose early instructions have been summarised several times, and the best-practices page warns that "instructions from early in the conversation can get lost." If the agent has forgotten the spec, stop and restart with the spec.

What the research says about stopping early

The obvious objection is that stopping early kills runs that would have succeeded. The FailFast paper (arXiv:2608.03222) measured that trade-off directly on SWE-bench Verified. Its abstract starts from the observation that "Failed runs tend to be longer and exhibit redundant exploration or looping, suggesting that some failures may be detectable before completion." The authors trained a 0.6 billion parameter monitor to predict failure from the visible prefix of a trajectory, and report that it "saves 14.6%-20.4% of execution tokens at a target 5% false-positive rate" across four policies including a closed-API model. The part that matters for practice is the restart: at a 25% false-positive rate, restarting fresh with the interrupted diff offered as an optional overlay "raises Qwen3.6-27B resolution from 66.6% to 71.8%, whereas cold restart reaches only 66.8%."

Read that as two rules. First, a run that is looping is more likely to fail than to recover, so stopping it is usually the right call even with a crude detector. Second, when you restart, keep the diff and throw away the conversation. The diff is the part that might be useful; the transcript is the part that taught the model to loop.

The manual controls map onto this. In an interactive Claude Code session the best-practices page lists Esc to stop mid-action with context preserved, and Esc twice or /rewind to "restore previous conversation and code state." Rewind is the cold restart; keeping the edits and clearing the conversation is the warm one.

The analyser

The shipped script reads a stream-json transcript, the newline-delimited JSON that claude -p ... --output-format stream-json --verbose writes, and computes four numbers: main-conversation turns (assistant messages whose parent_tool_use_id is null, so subagent chatter is excluded), the largest count of identical tool calls (same tool name, same input), the longest run of consecutive tool results flagged is_error, and total_cost_usd from the final result line. Each has a threshold flag, and crossing any of them exits 1.

I did not have a failed session on disk to feed it, so I constructed a transcript in a temporary folder with the documented message shapes: one file read, four failing test runs with no edit between them, one edit, one more failing test run, and a result line with a cost of 1.42 dollars. That is a fixture, not a real session. This is the real output from this session, Python 3.13 on Windows:

$ python when-to-stop-a-coding-agent.py constructed.jsonl
turns=7 tool_calls=7 tool_results=7 max_repeat=5 error_streak=4 cost_usd=1.42
  repeated x5: Bash {"command": "python -m unittest test_tally"}
STOP: a tool call repeated 5 times (limit 3); 4 consecutive tool errors (limit 4)
exit=1

$ python when-to-stop-a-coding-agent.py constructed.jsonl --max-repeats 6 --max-error-streak 6
turns=7 tool_calls=7 tool_results=7 max_repeat=5 error_streak=4 cost_usd=1.42
  repeated x5: Bash {"command": "python -m unittest test_tally"}
CONTINUE: no stop signal
exit=0

Note what the repeat counter does and does not catch. Five identical test runs is flagged even though one edit happened in the middle, because the counter is per call, not per streak. That is intentional: running the same test five times in seven turns is the signature regardless of the one edit. It will not catch an agent that varies the command trivially, python -m unittest test_tally -v then without -v; normalising commands is the first improvement to make if your agent does that.

Wired into a scripted run, the script sits after the harness and decides whether the wrapper restarts:

claude -p "$(cat SPEC.md)" --max-turns 40 --max-budget-usd 3 \
  --output-format stream-json --verbose | tee run.jsonl >/dev/null
python when-to-stop-a-coding-agent.py run.jsonl --max-repeats 3 || echo "restart with the diff, not the transcript"

The signs that need a human

Some sideways sessions have no loop in them. The agent widens the task: the spec said one file and the diff touches nine. It reports done and the diff checklist shows a skipped test. It asks a question the spec already answered, which usually means the spec was compacted away. None of these show up in tool-call counts. They show up when you read the diff, which is why the budget for a session should include the ten minutes it takes to read what it produced.

I have not measured my own stop thresholds against outcomes; the defaults in the script (40 turns, 3 repeats, 4 errors, 5 dollars) are the numbers I use, not numbers I have validated. The FailFast figures are the only controlled ones in this article, and they were measured on a benchmark with a trained monitor, not on a regex over a transcript. The time-to-green method records turns and cost per task so that, after enough runs, the thresholds can be set from data rather than habit.

Code and data

Sources

  1. Anthropic, "CLI reference" (Claude Code docs, read 2026-09-05)
  2. Anthropic, "Best practices for Claude Code" (Claude Code docs, read 2026-09-05)
  3. Anthropic, "How Claude Code works" (Claude Code docs, read 2026-09-05)
  4. SWE-agent/mini-swe-agent, "src/minisweagent/agents/default.py at commit 04d809ceab9d (read 2026-09-05)"
  5. OpenHands, "Condenser" (Software Agent SDK docs, read 2026-09-05)
  6. Wang, Lyu, He, Yang, Zhong, Harel, Lo, "Fail-Fast, Restart-Smart: Early Failure Prediction and Restart for SWE Agentic Tasks" (arXiv:2608.03222)
  7. omnigent-ai/omnigent, "README at commit 83cc3e7113df (read 2026-09-05)"