BeClaude
GuideBeginnerAgents2026-05-20

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 and troubleshooting tips for robust applications.

Quick Answer

This guide explains Claude API stop_reason values (end_turn, tool_use, max_tokens, stop_sequence) and how to handle them in your code. You'll learn to detect empty responses, manage tool call loops, and build robust applications that respond correctly to each termination type.

stop_reasonMessages APIerror handlingtool useClaude API

Introduction

When you send a request to the Claude Messages API, the 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 essential for building applications that handle different response types appropriately, 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 fix empty responses
  • Best practices for tool-using agents

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
  }
}

The stop_reason field can have one of four values: end_turn, tool_use, max_tokens, or stop_sequence. Let's explore each.

Stop Reason Values

end_turn

Meaning: Claude finished its response naturally. The model decided it had completed its turn and didn't need to continue. When it occurs: This is the most common stop reason. It happens when Claude has fully answered a question, completed a task, or simply said "I'm done." How to handle it: Process the response content as final. 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": # Process the complete response print(response.content[0].text)

tool_use

Meaning: Claude wants to call a tool. The response will contain one or more tool_use content blocks. When it occurs: When you've provided tools in your request and Claude decides it needs to use one (or more) to complete the task. How to handle it: Extract the tool calls, execute them, and send the results back in a new user message with tool_result blocks.
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)
            
            # Append tool result to messages
            messages.append({
                "role": "user",
                "content": [{
                    "type": "tool_result",
                    "tool_use_id": tool_use_id,
                    "content": str(result)
                }]
            })
    
    # Continue the conversation
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=messages
    )

max_tokens

Meaning: Claude stopped because it reached the max_tokens limit you set. When it occurs: The response was cut off mid-generation. The content may be incomplete. How to handle it: You have two options:
  • Increase max_tokens if you need longer responses.
  • Send the partial response back to let Claude continue from where it left off.
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: Continue from where it stopped
    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=1024,
        messages=messages
    )

stop_sequence

Meaning: Claude stopped because it encountered a custom stop sequence you defined in your request. When it occurs: You've set stop_sequences in your API call, and Claude generated one of them. How to handle it: The content up to (but not including) the stop sequence is valid. Process it as a complete response.
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    stop_sequences=["\n\nHuman:"],
    messages=[{"role": "user", "content": "Tell me a story"}]
)

if response.stop_reason == "stop_sequence": # The response stopped because it hit the stop sequence print(f"Stopped at sequence: {response.stop_sequence}") print(response.content[0].text)

Handling Empty Responses with end_turn

Sometimes Claude returns an empty response (2-3 tokens with no content) with stop_reason: "end_turn". This typically happens in tool-using scenarios.

Common Causes

  • Adding text blocks 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 approach: Adding text immediately after tool_result:
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 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"}
    ]}  # Just the tool_result, no additional text
]

Handling Empty Responses When They Occur

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 with your response."
        })
        return client.messages.create(
            model="claude-opus-4-7",
            max_tokens=1024,
            messages=messages
        )
    
    return response

Building a Robust Response Handler

Here's a complete handler that processes all stop reasons correctly:

from anthropic import Anthropic

client = Anthropic() messages = [{"role": "user", "content": "What's the weather in Tokyo?"}]

def handle_response(response, messages): if response.stop_reason == "end_turn": # Natural completion - process the response print("Final response:", response.content[0].text) return response elif response.stop_reason == "tool_use": # Claude wants to use tools 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) }] }) # Continue the conversation new_response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=messages ) return handle_response(new_response, messages) elif response.stop_reason == "max_tokens": # Response was cut off - continue messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": "Please continue."}) new_response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=messages ) return handle_response(new_response, messages) elif response.stop_reason == "stop_sequence": # Custom stop sequence encountered print("Response stopped by custom sequence:", response.stop_sequence) return response

Initial call

response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=messages )

final_response = handle_response(response, messages)

Best Practices

  • Always check stop_reason: Don't assume a response is complete just because you got a 200 status code.
  • Handle tool_use in a loop: Tool-using agents may need multiple rounds of tool calls to complete a task.
  • Set appropriate max_tokens: If you expect long responses, set max_tokens high enough to avoid truncation.
  • Log stop_reason: For debugging and monitoring, always log the stop reason along with the response.
  • Test with empty responses: Build your application to gracefully handle empty end_turn responses, especially when using tools.

Key Takeaways

  • Four stop reasons: end_turn (natural completion), tool_use (wants to call a tool), max_tokens (hit token limit), and stop_sequence (hit custom stop sequence).
  • Handle tool_use by executing tools and sending results back in a new user message with tool_result blocks.
  • Prevent empty responses by not adding text blocks after tool_result blocks in your messages array.
  • For max_tokens, either increase the limit or send the partial response back with a continuation prompt.
  • Build a recursive handler that processes all stop reasons correctly, especially for tool-using agents that may need multiple rounds.