Skip to main content

The Agent Loop

A turn is everything that happens from your prompt to the final answer. Inside a turn, the model asks for tools, Claude Code runs them, and the model reads the results. This repeats until the model answers without a tool call. This page walks through each stage and shows where you can step in.

At a Glance​

QuestionAnswer
Can you use it?✅ Yes. Every prompt starts a turn.
Can you change it?✅ Yes, at fixed points. Use hooks, mod events, permission rules, and a few env vars.
Can you turn it on or off?Each hook and rule has its own switch. The loop itself always runs.
Where does it live?Inside the program. Hook settings are in settings.json files.
StatusDocumented Undocumented

The Turn at a Glance​

The Stages​

Sources: Hooks Reference: Hook Lifecycle and Mods Reference for the events. Source: the program for the rest.

StageStatusWhat happensWhere you can act
1. InputDocumentedClaude Code reads your text. A message you type during a turn joins at a safe point.UserPromptSubmit hook, prompt.submit mod event
2. Command expansionDocumentedA slash command or skill name turns into a prompt.UserPromptExpansion hook
3. Context pickupDocumentedClaude Code adds nested memory, @ mentions, and file-change notes.InstructionsLoaded hook, prompt.mention and prompt.attachment mod events
4. Message prepUndocumentedLarge tool results become references. Claude Code looks up the facts of the model.PreModelSwitch and PostModelSwitch hooks
5. Auto compactionDocumentedClaude Code compacts the conversation if it is over the threshold. See Context and Compaction.PreCompact and PostCompact hooks
6. AttachmentsUndocumentedClaude Code adds reminders and notes about the mode.prompt.attachment mod event
7. Request buildDocumentedClaude Code builds the system, tools, and messages parts. See Request and Cache.prompt.section, prompt.compose, and tool.describe mod events
8. StreamDocumentedClaude Code sends the request and reads the events, with retries and fallbacks. See Models.MessageDisplay hook
9. ToolsDocumentedEach tool call passes the permission check and then runs.Tool hooks (next section)
10. Next roundUndocumentedThe tool results join the messages, and the loop starts again at stage 3.PostToolBatch hook
11. StopDocumentedWith no tool call, the Stop hooks run. A hook that blocks sends its reason back to the model.Stop and StopFailure hooks

The mod events turn.start, turn.step, and turn.complete wrap the whole turn. See the Mod API pages in Extend.

The Tool Stage​

Sources: Hooks Reference and Environment Variables.

StepStatusWhat happens
Permission checkDocumentedYour rules and the permission mode decide to allow, ask, or deny. See Permission Flow in this section.
PreToolUse hookDocumentedThe hook can block or change the call.
PermissionRequest hookDocumentedThe hook runs when a decision is needed.
RunDocumentedRead-only tools and subagents run in parallel, up to CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY (default 10). Other tools run one after another.
PostToolUse and PostToolUseFailure hooksDocumentedThey run after each call.
PostToolBatch hookDocumentedIt runs after a batch of parallel calls and before the next model call. It can stop the loop.

Recovery Inside the Loop​

The loop does not fail on every error. It tries to fix the problem and go on.

CaseStatusWhat Claude Code does
The output stops at max_tokensUndocumentedIt adds a nudge message and asks again, a limited number of times.
The response is cut offUndocumentedIt does the same.
A tool_use block has no valid callUndocumentedIt drops the reply and asks once more. Then it ends with a message that the tool call could not be parsed.
The model sends only thinkingUndocumentedIt sends one nudge.
A safety refusalDocumentedIt runs again on the fallback model of that category. See Models.
The context is too longDocumentedIt lowers max_tokens or compacts.
Autocompact refills 3 times in a rowDocumentedIt ends the turn with an error that starts Autocompact is thrashing. See Troubleshooting in the official docs.
A Stop hook blocksDocumentedIt feeds the reason back. After CLAUDE_CODE_STOP_HOOK_BLOCK_CAP blocks in a row (default 8), it ends the turn.
--max-turns is reachedDocumentedIt ends the run. See the CLI Reference in the official docs.

How a Turn Ends​

The loop returns a reason, and Claude Code records it in telemetry. The chat does not show it. A result message of the Agent SDK and of --output-format stream-json carries it as terminal_reason. All reasons are Undocumented, because only the Agent SDK pages name some of them.

reasonMeaning
completedThe turn ended in the normal way.
max_turnsThe turn limit was reached.
aborted_streaming, aborted_toolsYou stopped the turn (for example with Esc) while the model streamed or tools ran.
hook_stopped, stop_hook_preventedA hook stopped the turn.
tool_deferredA hook deferred a tool call.
tool_deferred_unavailableA deferred tool call could not be resumed.
background_requestedThe turn moved to a background session.
blocking_limitThe context hit the blocked level. See Context and Compaction.
rapid_refill_breakerAutocompact refilled the context too fast.
model_error, api_error, image_errorThe API or an image failed.
malformed_tool_use_exhaustedThe model sent two bad tool calls in a row.
prompt_too_longThe prompt did not fit the context window.
budget_exhaustedThe budget for the run was used up.
structured_output_retry_exhaustedThe model did not give valid structured output after the allowed retries.
turn_setup_failedClaude Code failed to set up the turn.

What You Can Change​

GoalHow
Check or edit every prompt before it runsA UserPromptSubmit hook.
Block or rewrite a tool callA PreToolUse hook or a permission rule.
Force the model to keep workingA Stop hook that blocks. Mind CLAUDE_CODE_STOP_HOOK_BLOCK_CAP.
Limit tool parallelismSet CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY.
Limit the number of turnsUse --max-turns in print mode.
Rewrite a part of the system promptA mod listening to prompt.section or prompt.compose. See The System Prompt.
Change the compaction pointSee Context and Compaction.

Example​

This hook in .claude/settings.json runs a script before every Bash call. If the script exits with code 2, the call is blocked and the reason goes back to the model.

.claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": ".claude/hooks/check-bash.sh" }]
}
]
}
}