Safety Limits: maxTurns and maxBudgetUsd
Cap how many turns an agent can run and how much it can spend per session. Two options that prevent infinite loops and runaway costs in any deployed agent.
Two failure modes to protect against
An agent running without supervision can fail in two ways that matter to operators:
Infinite loops. A complex prompt causes the agent to keep taking turns without finishing. The task never completes. You do not notice until you check the bill.
Budget overruns. A single agent run consumes far more tokens than expected because the task expanded or the agent made poor decisions about scope.
Both are preventable with two options: maxTurns and maxBudgetUsd.
Setting turn limits
maxTurns caps how many reasoning-and-tool-use cycles the agent can go through before stopping:
for await (const message of query(messages(), {
model: "claude-sonnet-4-5",
maxTurns: 30,
permissionMode: "bypassPermissions",
dangerouslyAllowBypassPermissions: true,
})) {
// handle messages
}
When the agent hits the turn limit, it stops and the result message includes a subtype of "max_turns" instead of "success":
if (message.type === "result") {
if (message.subtype === "success") {
console.log("Completed:", message.result);
} else if (message.subtype === "max_turns") {
console.log("Stopped at turn limit.");
}
}
Setting budget limits
maxBudgetUsd sets a dollar ceiling for a single session:
for await (const message of query(messages(), {
model: "claude-sonnet-4-5",
maxTurns: 30,
maxBudgetUsd: 1.00,
permissionMode: "bypassPermissions",
dangerouslyAllowBypassPermissions: true,
})) {
// handle messages
}
The session stops when it hits whichever limit comes first. If the budget is hit, the result message includes a subtype of "maxbudgetusd".
You can set either limit alone, both together, or neither. They work independently.
Calibrating the values
There is no universal right answer. The values depend on what the agent is doing:
| Agent type | maxTurns guidance | maxBudgetUsd guidance |
|---|---|---|
| Simple Q&A, single tool call | 5-10 | $0.10 |
| Research with web search | 15-25 | $0.50 |
| Complex multi-step analysis | 30-50 | $1-2 |
| Long planning or document work | 50-100 | $2-5 |
Start conservative. Check the actual turn count and cost in your result messages. Adjust upward as you understand the typical workload.
if (message.type === "result" && message.subtype === "success") {
console.log("Turns:", message.num_turns);
console.log("Cost:", message.total_cost_usd);
}
For development, set maxTurns: 1 intentionally to observe where the agent stops. This helps you understand the execution structure before setting real limits.
Why these matter for deployed agents
During development, you run the agent, watch it work, and stop it manually if something goes wrong. In production — a Slack bot running overnight, an automation triggered by a webhook — nobody is watching.
An agent that loops 500 times on a malformed prompt is not hypothetical. It is a billing event. maxTurns: 30 and maxBudgetUsd: 1.00 cost you nothing on successful runs and prevent the worst outcomes on failed ones.
Add them to every deployed agent before the first real user sends a message.
> Combined with tool scoping: maxBudgetUsd and maxTurns work best alongside a tight tools array. Budget limits prevent runaway costs. Tool lists prevent the agent from taking actions outside its intended scope. Together they give you a meaningful safety boundary around any autonomous agent you deploy.
---
Author: FractionalSkill