The full answer
Put hard limits around the loop in code: a maximum number of turns, a spending budget and a wall-clock timeout. Then find out why it loops. The usual causes are tool errors the model cannot understand, a goal with no clear end, and a tool that returns too much. Limits stop the damage; better tools and prompts stop the cause.
1. Hard limits (never optional)
- Turns. The Claude Agent SDK has
max_turns(maxTurnsin TypeScript), which counts tool-use turns. With no setting there is no limit. When it is hit, the run ends with the result subtypeerror_max_turns. - Budget.
max_budget_usd(maxBudgetUsd) stops the run once its cost passes a figure, with the subtypeerror_max_budget_usd. The check runs between turns, so a run can go over by about one response. Set it a little below what you can afford. - Time. A timeout on the whole run and on each tool call, so one hung command cannot block everything.
- Tokens per response.
max_tokenscaps each reply.
Anthropic's "Building effective agents" lists a maximum number of iterations among the stopping conditions an agent should have, because errors compound over long runs.
2. Detect loops
- The same tool with the same input 3 times in a row: stop, or return a result that says "you already tried this; it failed because …".
- No progress: no new file changed, no new fact found for N turns.
- Log every turn with its tool, input size, output size and cost, so a stuck run is visible on a dashboard rather than on the invoice.
3. Fix the causes
- Unclear errors. "Error 500" makes the model retry blindly. "Table sales.orders does not exist; tables in sales are: order_lines, customers" lets it correct itself.
- No finish line. Say in the task what done looks like ("stop when all tests pass, or after 2 failed fixes, and report what you tried").
- Huge outputs. A tool returning 50,000 lines fills the context, and the model loses track. Truncate and paginate.
- Too much freedom. If the path is known, use a fixed workflow for that part.
4. Fail usefully
When a limit stops the run, return what was done and why it stopped, and keep the session so a person can resume it. Both "hit max turns after fixing 3 of 5 tests" and a session id are more useful than a blank failure.
A good closing line
"Limits are for safety, and transcripts are for fixing. I read the runs that hit a limit and change the tool or the prompt so the next one doesn't."
Last updated 16 October 2026
Sources