Mastering Claude API Stop Reasons: A Practical Guide to Handling Response Terminations
Learn how to interpret and handle Claude API stop_reason values like end_turn, tool_use, and max_tokens. Includes code examples, empty response fixes, and best practices for robust applications.
This guide explains Claude API stop_reason values (end_turn, tool_use, max_tokens, stop_sequence) and how to handle each in your code. You'll learn to detect empty responses, manage tool calls, and build robust conversation loops.
Introduction
When you send a request to the Claude API, the response includes a stop_reason field that tells you why the model stopped generating. Understanding these values is essential for building reliable applications—whether you're handling tool calls, managing long conversations, or debugging unexpected empty responses.
Unlike API errors (which indicate a failure), stop_reason is part of every successful response. It's your signal for what to do next: continue the conversation, execute a tool, or present the final answer.
The stop_reason Field
Every successful Messages API response includes a stop_reason field. Here's a typical response structure:
{
"id": "msg_01234",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "Here's the answer to your question..."
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}
Stop Reason Values
Claude can stop for four distinct reasons. Each requires a different response from your application.
1. end_turn
Meaning: Claude finished its response naturally. The model decided it has completed its turn in the conversation.
When it occurs: This is the most common stop reason. It happens after Claude answers a question, provides information, or completes a thought.
How to handle: Simply present the response to the user. No further action is needed.
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}]
)
if response.stop_reason == "end_turn":
print(response.content[0].text)
2. tool_use
Meaning: Claude wants to call a tool (function) you've provided. The response content will include one or more tool_use blocks.
When it occurs: When Claude determines it needs external data or computation—like fetching weather data, running a calculation, or querying a database.
How to handle: Extract the tool call details, execute the tool in your environment, then send the result back as a tool_result block.
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=[{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}],
messages=[{"role": "user", "content": "What's the weather in Paris?"}]
)
if response.stop_reason == "tool_use":
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
tool_use_id = block.id
# Execute your tool logic here
result = execute_tool(tool_name, tool_input)
# Then send result back in next request
3. max_tokens
Meaning: Claude stopped because it reached the max_tokens limit you set in your request. The response is incomplete.
When it occurs: When the model needs more tokens to finish its thought, or when your max_tokens is set too low for the task.
How to handle: Send the response back to Claude in a new message to let it continue. This is especially important for long-form generation or complex reasoning.
if response.stop_reason == "max_tokens":
# Append Claude's response and ask it to continue
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": "Please continue."})
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=2048, # Consider increasing the limit
messages=messages
)
4. stop_sequence
Meaning: Claude encountered a custom stop sequence you defined in your request. This is useful for structured outputs or when you want Claude to stop at a specific delimiter.
When it occurs: When you've set stop_sequences in your API request and Claude generates one of those sequences.
How to handle: The response is complete per your custom criteria. Process it as needed. The stop_sequence field in the response will contain the actual sequence that triggered the stop.
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
stop_sequences=["\n\n---END---"],
messages=[{"role": "user", "content": "Write a short poem and end with ---END---"}]
)
if response.stop_reason == "stop_sequence":
print(f"Stopped at sequence: {response.stop_sequence}")
print(response.content[0].text)
Handling Empty Responses with end_turn
A common gotcha: Claude sometimes returns an empty response (2-3 tokens, no content) with stop_reason: "end_turn". This typically happens in tool-use scenarios.
Why It Happens
- Adding text after tool results: If you insert a text block immediately after a
tool_result, Claude may interpret this as the user taking over and end its turn. - Sending Claude's completed response back unchanged: If Claude already decided it's done, sending the same response back won't change its mind.
How to Prevent Empty Responses
Incorrect approach:messages = [
{"role": "user", "content": "Calculate the sum of 1234 and 5678"},
{"role": "assistant", "content": [
{
"type": "tool_use",
"id": "toolu_123",
"name": "calculator",
"input": {"operation": "add", "a": 1234, "b": 5678}
}
]},
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "toolu_123", "content": "6912"},
{"type": "text", "text": "Here's the result"} # ❌ Don't add text here
]}
]
Correct approach:
messages = [
{"role": "user", "content": "Calculate the sum of 1234 and 5678"},
{"role": "assistant", "content": [
{
"type": "tool_use",
"id": "toolu_123",
"name": "calculator",
"input": {"operation": "add", "a": 1234, "b": 5678}
}
]},
{"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "toolu_123", "content": "6912"} # ✅ Just the result
]}
]
Recovery Strategy
If you still get empty responses, use a continuation prompt:
def handle_empty_response(client, messages):
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=messages
)
if response.stop_reason == "end_turn" and not response.content:
# Add a continuation prompt in a NEW user message
messages.append({"role": "user", "content": "Please continue"})
response = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=messages
)
return response
Building a Complete Conversation Loop
For production applications, you'll want to handle all stop reasons in a single loop:
def converse_with_claude(client, messages, tools=None):
while True:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
messages=messages,
tools=tools
)
if response.stop_reason == "end_turn":
# Claude is done—return the final response
return response
elif response.stop_reason == "tool_use":
# Execute tools and append results
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result)
}]
})
elif response.stop_reason == "max_tokens":
# Let Claude continue
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": "Please continue."})
elif response.stop_reason == "stop_sequence":
# Custom stop reached—return as-is
return response
Best Practices
- Always check
stop_reasonbefore processing content. Don't assumeend_turnmeans the response is complete—it could be empty. - Never add text after
tool_resultblocks. Send tool results directly without commentary. - Use continuation prompts for
max_tokens. Don't just increasemax_tokensblindly—let Claude continue naturally. - Log
stop_reasonfor debugging. It helps identify patterns in your application's behavior. - Handle empty responses gracefully. Implement the recovery pattern shown above to avoid silent failures.
Key Takeaways
- Four stop reasons exist:
end_turn(natural completion),tool_use(tool call needed),max_tokens(output truncated), andstop_sequence(custom delimiter reached). - Empty responses with
end_turnare common in tool-use scenarios—prevent them by not adding text aftertool_resultblocks. - Always check
stop_reasonin your application logic to determine the next action, whether that's presenting output, executing a tool, or continuing the conversation. - Build a conversation loop that handles all stop reasons to create robust, production-ready Claude integrations.
- Use continuation prompts ("Please continue") to recover from
max_tokensstops or empty responses.