Mastering Claude's Stop Reasons: A Practical Guide to Handling API Responses
Learn how to interpret and handle Claude's stop_reason field in the Messages API. Includes code examples, troubleshooting empty responses, and best practices for robust applications.
This guide explains Claude's stop_reason field—end_turn, tool_use, max_tokens, and stop_sequence—and how to handle each in your application. You'll learn to detect empty responses, prevent premature stops, and build robust conversational flows.
Introduction
When building applications with the Claude API, every successful response includes a stop_reason field that tells you why the model stopped generating. This isn't an error—it's a signal. Understanding these signals is the difference between a brittle prototype and a production-ready application.
In this guide, you'll learn:
- What each
stop_reasonvalue means - How to handle them in Python and TypeScript
- How to prevent and recover from empty responses
- Best practices for tool-using agents
The stop_reason Field
Every successful response from the Messages API includes a stop_reason field. Here's a typical response:
{
"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
}
}
The stop_reason can be one of four values:
| Value | Meaning |
|---|---|
end_turn | Claude finished naturally |
tool_use | Claude wants to call a tool |
max_tokens | Output hit the token limit |
stop_sequence | A custom stop sequence was triggered |
Handling end_turn
end_turn is the most common stop reason. It means Claude has completed its response and is waiting for the next user input. In most cases, you can simply display the response to the user.
Python Example
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":
# Process the complete response
print(response.content[0].text)
TypeScript Example
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
async function main() {
const response = await client.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 1024,
messages: [
{ role: 'user', content: 'Hello!' }
]
});
if (response.stop_reason === 'end_turn') {
console.log(response.content[0].text);
}
}
The Empty Response Problem
Sometimes Claude returns an empty response (2-3 tokens with no content) with stop_reason: "end_turn". This typically happens in tool-use scenarios when:
- You add text blocks immediately after
tool_resultblocks - You send Claude's completed response back without adding anything new
#### How to Prevent Empty Responses
Incorrect approach: Adding text after tool resultsmessages = [
{"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 do this
]}
]
Correct approach: Send tool results directly without additional text
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"}
# ✅ No text block here
]}
]
#### Recovering from Empty Responses
If you still get empty responses after fixing the above, use a continuation prompt:
def handle_empty_response(client, messages):
response = client.messages.create(
model="claude-opus-4-20250514",
max_tokens=1024,
messages=messages
)
if response.stop_reason == "end_turn" and not response.content:
# ❌ Don't just retry with the same messages
# Claude already decided it's done
# ✅ Add a continuation prompt in a NEW user message
messages.append({
"role": "user",
"content": "Please continue"
})
response = client.messages.create(
model="claude-opus-4-20250514",
max_tokens=1024,
messages=messages
)
return response
Handling tool_use
When stop_reason is tool_use, Claude has decided to call a tool. Your application must:
- Parse the tool call from the response content
- Execute the tool (e.g., call an API, query a database)
- Return the result as a
tool_resultblock - Continue the conversation
Python Example
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=[
{
"name": "get_weather",
"description": "Get the 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 the tool
result = execute_tool(tool_name, tool_input)
# Add tool result to conversation
messages.append({
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": str(result)
}
]
})
Handling max_tokens
When stop_reason is max_tokens, Claude hit the token limit you set. The response is truncated. To continue:
if response.stop_reason == "max_tokens":
# Add Claude's partial response to the conversation
messages.append({
"role": "assistant",
"content": response.content
})
# Ask Claude to continue
messages.append({
"role": "user",
"content": "Please continue from where you left off."
})
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=2048, # Increase the limit
messages=messages
)
Handling stop_sequence
This occurs when Claude encounters a custom stop sequence you defined. For example, if you set stop_sequences: ["\n\nHuman:"], Claude will stop when it generates that pattern. Handle this similarly to max_tokens—append the partial response and continue.
Best Practices
- Always check
stop_reasonbefore processing content. Don't assume the response is complete. - Handle all four stop reasons in your application logic. Each requires a different action.
- Never add text after
tool_resultblocks. This is the most common cause of empty responses. - Use continuation prompts for empty responses or truncated outputs.
- Log
stop_reasonfor debugging and monitoring.
Key Takeaways
stop_reasonis not an error—it's a signal that tells you what to do next.end_turnmeans Claude is done; display the response or wait for user input.tool_usemeans Claude wants to call a tool; execute it and return results.max_tokensandstop_sequencemean the response is truncated; continue the conversation.- Empty responses with
end_turnare usually caused by adding text aftertool_resultblocks—fix the message structure, then use a continuation prompt to recover.