end_turn
APIDefinition
A stop_reason value indicating Claude finished its response naturally without hitting a limit or requesting a tool. In agentic loops, this signals the loop should stop and the final response should be presented to the user.
Example Usage
if response.stop_reason == 'end_turn': break # Task complete, present response
In Depth
`end_turn`: The Natural Completion Signal
end_turn is the stop_reason value that means Claude finished generating its response on its own terms — no token limit was reached, no tool was requested, no stop string was hit, and no safety boundary was encountered. In a single-turn exchange, end_turn simply means you have a complete answer. In an agentic loop, it is the signal that the model considers the task done and the loop should exit.
The importance of end_turn is not just what it means — it is that you should only present the final response to the user, or mark the task complete, when you receive this value. Any other stop_reason means something else is happening: a tool needs to run, the output was truncated, the model refused, or the context is exhausted.
`end_turn` in a Tool-Use Loop
A well-structured agentic loop treats end_turn as the sole exit condition for successful completion:
import anthropic
client = anthropic.Anthropic()
messages = [{"role": "user", "content": user_task}]
while True:
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=4096,
tools=tool_definitions,
messages=messages
)
if response.stop_reason == "end_turn":
# Task complete — extract text and break
final_text = next(
block.text for block in response.content
if block.type == "text"
)
break
elif response.stop_reason == "tool_use":
# Execute each requested tool and collect results
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
# Append assistant turn and tool results, then loop
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
# max_tokens, refusal, model_context_window_exceeded, etc.
raise RuntimeError(f"Unexpected stop: {response.stop_reason}")Note that all tool_result blocks for a parallel tool call must go into a single user message — not separate messages per tool.
Why Not Check Content Instead?
A tempting shortcut is to check whether the response text contains a phrase like "I have completed" or "Done" as the loop exit condition. This is unreliable: Claude may use such phrases mid-task, and it may complete a task without them. stop_reason is a machine-readable, structured signal explicitly designed for loop control — use it.
Relationship to Other Stop Reasons
end_turn sits at the top of the priority hierarchy for loop exit logic. See stop_reason for the full set of values and when each is appropriate. In the context of the agentic loop lifecycle, end_turn marks the transition from the execution phase back to the presentation layer.
How It's Tested & Common Confusions
Exam questions typically present loop pseudocode and ask you to identify the correct exit condition. end_turn is always the answer for successful natural completion. You may also be tested on why checking response text content is a wrong alternative, or on what to do if the loop never receives end_turn (diagnose: either the model is always requesting tools, hitting max_tokens, or the loop has a continuation bug).
Frequently Asked Questions
Can `end_turn` appear when there are tool_use blocks in the response?
No. When the model requests a tool call, `stop_reason` is `tool_use`, not `end_turn`. The two values are mutually exclusive in a single response. `end_turn` means the assistant turn contained only text (or thinking) content — no pending tool calls.
What if my loop runs for many iterations but never reaches `end_turn`?
This usually indicates one of three problems: (1) the model keeps calling tools in a cycle because it hasn't received sufficient tool results to conclude — check that your tool results are correctly formatted with matching `tool_use_id` values; (2) you have a bug in the message accumulation logic and the model isn't seeing prior context; or (3) the task is genuinely unbounded and you need a max-iteration guard to avoid an infinite loop. Always add a maximum iteration count as a safety backstop alongside the `end_turn` check.
Is `end_turn` guaranteed when Claude thinks it has answered the question?
Yes — if the model generates a final text response without hitting any limit or triggering a tool call, `stop_reason` will be `end_turn`. This is the model's only way to signal completion. However, 'completion' is the model's own assessment; for task-critical applications, add application-level validation (e.g., a [validation loop](/glossary/validation-loop)) after receiving `end_turn` to confirm the output actually meets your requirements.