BeClaude
GuideBeginner2026-05-06

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. Covers end_turn, tool_use, max_tokens, and empty response prevention with code examples.

Quick Answer

This guide explains Claude’s stop_reason field—end_turn, tool_use, max_turns, and stop_sequence—and shows how to handle each in your code. You’ll also learn to prevent and recover from empty responses when using tools.

Claude APIstop_reasonMessages APItool useerror handling

Introduction

When you call the Claude Messages API, every successful response includes a stop_reason field. This small but critical piece of data tells you why Claude stopped generating text. Understanding these values is essential for building reliable, production-ready applications—especially when working with tools, streaming, or multi-turn conversations.

In this guide, you’ll learn:

  • What each stop_reason value means
  • How to handle them in Python and TypeScript
  • How to prevent and recover from empty responses
  • Best practices for robust API integration

The stop_reason Field

Every successful response from the Messages API contains a stop_reason field. It is not an error—it’s a signal that Claude completed its response generation for a specific reason.

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

end_turn

The most common stop reason. Claude finished its response naturally—it decided the conversation turn was complete. This usually means you can display the final 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

Claude stopped because it wants to call a tool. The response content will include a tool_use block with the tool name and input. Your application must execute the tool and return the result in a new user message with role: "user" and content of type tool_result.

Handling tool_use in Python:
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    messages=messages,
    tools=[{
        "name": "get_weather",
        "description": "Get the current weather for a city",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {"type": "string"}
            },
            "required": ["location"]
        }
    }]
)

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 tool and get result 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) }] }) # Continue the conversation response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=messages, tools=tools )

max_tokens

Claude reached the token limit you set in max_tokens. The response is incomplete. You should either:

  • Increase max_tokens for future requests
  • Send the response back to Claude with a prompt like "Please continue" to get more output
Example recovery:
if response.stop_reason == "max_tokens":
    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,  # Increase limit
        messages=messages
    )

stop_sequence

Claude encountered a custom stop sequence you defined in the request. This is useful for structured outputs—for example, stopping generation after a closing XML tag.

Example:
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    stop_sequences=["</answer>"],
    messages=[{"role": "user", "content": "Write a short answer in XML: <answer>...</answer>"}]
)

if response.stop_reason == "stop_sequence": print("Stopped at custom sequence:", response.stop_sequence)

Handling Empty Responses with end_turn

Sometimes Claude returns an empty response (2–3 tokens, no content) with stop_reason: "end_turn". This typically happens in tool-use scenarios when Claude decides the assistant turn is complete.

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 remains 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 from Empty Responses

If you still get empty responses after fixing the message structure, 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: # ❌ 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

  • Always check stop_reason – Don’t assume end_turn. Handle all possible values.
  • Use a loop for tool_use – Claude may call multiple tools in sequence. Keep calling until you get end_turn.
  • Increase max_tokens for long responses – If you frequently hit max_tokens, raise the limit.
  • Avoid extra text after tool results – Send only the tool_result block to prevent empty responses.
  • Log stop reasons – For debugging and monitoring, log the stop_reason and stop_sequence for each response.

Conclusion

Understanding stop_reason is fundamental to building reliable Claude-powered applications. By handling each reason appropriately—continuing after tool_use, recovering from max_tokens, and preventing empty responses—you can create smooth, robust conversational experiences.

Key Takeaways

  • stop_reason tells you why Claude stopped – Always check it to decide the next action.
  • end_turn means the response is complete – Display it to the user.
  • tool_use requires you to execute a tool and return the result – Use a loop to handle multiple tool calls.
  • max_tokens means the response was cut off – Increase the limit or ask Claude to continue.
  • Prevent empty responses by sending only tool_result blocks – Never add extra text after tool results.