Mastering Claude’s Stop Reasons: Build Reliable API Responses Every Time
Learn how to interpret and handle Claude API stop reasons like end_turn, tool_use, and max_tokens. Includes code examples, empty response fixes, and best practices for production apps.
This guide explains Claude’s stop_reason field—end_turn, tool_use, max_tokens, stop_sequence—and how to handle each in your code. You’ll learn to prevent empty responses, retry correctly, and build robust conversational flows.
Introduction
When you call the Claude Messages API, every successful response includes a stop_reason field. This tiny piece of data tells you why Claude stopped generating—whether it finished naturally, called a tool, hit a token limit, or matched a custom stop sequence. Ignoring it can lead to broken conversations, empty replies, or infinite loops.
In this guide, you’ll learn:
- What each
stop_reasonvalue means - How to handle them in Python and TypeScript
- How to prevent and fix empty responses
- Best practices for production applications
The stop_reason Field Explained
The stop_reason field appears in every successful response from the Messages API. It is not an error—it’s a signal about how Claude completed its generation.
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
}
}
Stop Reason Values
end_turn
Meaning: Claude finished its response naturally. The model decided the assistant’s turn was complete.
This is the most common stop reason. In most cases, you can simply return 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":
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);
}
}
tool_use
Meaning: Claude stopped because it wants to call a tool. The response content will contain one or more tool_use blocks.
What to do: Extract the tool call, execute it, and send the result back as a tool_result in a new user message.
if response.stop_reason == "tool_use":
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
# Execute your tool logic here
result = execute_tool(tool_name, tool_input)
# Append tool_result to messages
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result)
}]
})
max_tokens
Meaning: Claude hit the max_tokens limit you set. The response may be cut off mid-sentence.
What to do: You have two options:
- Increase
max_tokensif the response is incomplete. - Continue the conversation by sending the assistant’s partial response back in a new user message with a prompt like “Please continue.”
if response.stop_reason == "max_tokens":
# Option 1: Increase max_tokens and retry
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096, # Increased limit
messages=messages
)
# Option 2: 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=1024,
messages=messages
)
stop_sequence
Meaning: Claude stopped because it generated one of the custom stop sequences you provided in the stop_sequences parameter.
What to do: The response is complete according to your custom criteria. Process it normally, but be aware that the stop_sequence field will contain the actual sequence that triggered the stop.
if response.stop_reason == "stop_sequence":
print(f"Stopped at sequence: {response.stop_sequence}")
# Process the response
Handling Empty Responses with end_turn
Sometimes Claude returns an empty response (2-3 tokens with no content) and stop_reason: "end_turn". This typically happens in tool-use scenarios.
Common Causes
- Adding text immediately after tool results – Claude learns to expect the user to always insert text after tool results, so it ends its turn to follow the pattern.
- Sending Claude’s completed response back without adding anything – Claude already decided it’s done, so it will remain done.
How to Prevent Empty Responses
Incorrect pattern: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 after tool_result
]}
]
Correct pattern:
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 tool_result
]}
]
Recovery Strategy
If you still get empty responses after fixing the above:
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:
# ❌ Don't just retry with the same messages
# ✅ 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
Best Practices for Production
- Always check
stop_reason– Never assume the response is complete or valid. - Handle all four stop reasons – Your code should branch on
end_turn,tool_use,max_tokens, andstop_sequence. - Implement retry logic with backoff – For
max_tokens, increase the limit or continue the conversation. For empty responses, add a continuation prompt. - Log stop reasons – In production, log every
stop_reasonto monitor for unexpected patterns. - Test with tool use – Empty responses are most common with tools. Always test your tool-handling loop.
Conclusion
Understanding stop_reason is essential for building reliable Claude-powered applications. By handling each value correctly—especially tool_use and max_tokens—you can create smooth, uninterrupted conversations. And by following the patterns to prevent empty responses, you’ll avoid frustrating dead ends in your chat flows.
Key Takeaways
end_turnmeans Claude finished naturally; process the response as complete.tool_usemeans Claude wants to call a tool; execute it and return the result.max_tokensmeans Claude hit the token limit; increasemax_tokensor ask Claude to continue.stop_sequencemeans a custom stop sequence was matched; check thestop_sequencefield for details.- Empty responses with
end_turnare usually caused by adding text after tool results; fix by sending only thetool_resultblock.