agent-stop-conditions
Agent stop conditions
Every autonomous run needs a hard stop that the runtime enforces. A limit written into the prompt is a request the agent may ignore; a limit enforced by the loop, the launching process or a hook is a stop. Set it before the run starts, and decide in advance what the caller does when it fires.
1. Use the native knobs first
Both caps default to no limit: "Without limits, the loop runs until Claude finishes on its own, which is fine for well-scoped tasks but can run long on open-ended prompts." And: "Setting a budget is a good default for production agents."
| Knob | Exact behaviour (quoted) | What it produces | Caller must |
|---|---|---|---|
SDK max_turns / maxTurns |
"counts tool-use turns only" | ResultMessage subtype error_max_turns |
treat as incomplete; never read result, which exists only on success; log num_turns, total_cost_usd, session_id |
SDK max_budget_usd / maxBudgetUsd |
caps the run "based on a spend threshold"; subagent spend counts toward it | subtype error_max_budget_usd |
same; escalate rather than rerun with a bigger cap by default |
CLI --max-turns |
"Limit the number of agentic turns (print mode only). Exits with an error when the limit is reached." | the CLI exits with an error | check the exit status before reading any output |
CLI --max-budget-usd |
"Maximum dollar amount to spend on API calls before stopping (print mode only)." On --continue / --resume, "totals restored from earlier runs don't count toward it." |
stop; then spawning another subagent fails with Budget limit reached (v2.1.217 or later) |
keep your own total across a resume chain; each resumed run starts a fresh cap |
| Session crash | a streaming session emits a final error_during_execution and exits |
subtype error_during_execution |
do not auto-retry; inspect first |
PreToolUse hook |
runs before a tool executes; a hook that rejects a call prevents it from executing and Claude receives the rejection message | the call never runs | the place to enforce your own checks (sections 2 to 5) inside the run |
--permission-prompts none (v2.1.259 or later) / permissionMode "dontAsk" |
"Pass none when nobody can answer, and Claude Code denies them instead." |
denied tool call | use on unattended runs so a prompt cannot stall the run forever |
All result subtypes: success, error_max_turns, error_max_budget_usd, error_during_execution, error_max_structured_output_retries. Branch on the subtype, not on whether text came back. A single-shot query() yields the result message and then raises (for example "Reached maximum number of turns"): catch the raise, keep the message. In a streaming session the budget accumulates across messages and "A /clear starts the budget over."