BeClaude
GuideBeginnerAgents2026-05-12

Mastering Claude API Stop Reasons: A Complete Guide to Handling Response Endings

Learn how to interpret and handle Claude API stop_reason values like end_turn, max_tokens, and tool_use. Includes code examples, empty response fixes, and best practices for robust applications.

Quick Answer

This guide explains the stop_reason field in Claude API responses, covering values like end_turn, max_tokens, and tool_use. You'll learn how to handle each scenario, prevent empty responses, and build robust conversational flows with practical Python code examples.

stop_reasonMessages APIerror handlingtool useClaude API

Mastering Claude API Stop Reasons: A Complete Guide to Handling Response Endings

When building applications with the Claude API, understanding why the model stopped generating its response is just as important as the response content itself. The stop_reason field in every successful API response tells you exactly why Claude finished its turn—and knowing how to handle each scenario is essential for creating robust, production-ready applications.

In this guide, you'll learn:

  • What stop_reason values exist and what they mean
  • How to handle each stop reason in your code
  • How to prevent and fix empty responses
  • Best practices for building conversational flows that handle all stop scenarios gracefully

Understanding the Stop Reason Field

Every successful response from the Messages API includes a stop_reason field. Unlike error responses (which indicate something went wrong), stop_reason tells you why Claude successfully completed its response generation.

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 several values, each requiring a different handling strategy.

Stop Reason Values and How to Handle Them

1. end_turn — Natural Completion

What it means: Claude finished its response naturally and is ready for the next user input. This is the most common stop reason. How to handle it: Simply process the response and wait for the next user message.
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) # Wait for next user input

2. max_tokens — Token Limit Reached

What it means: Claude stopped because it reached the max_tokens limit you set. The response may be incomplete. How to handle it: You have two options:
  • Increase max_tokens if you need longer responses
  • Continue the conversation by sending Claude's response back as part of the message history, allowing it to continue
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "Write a long essay..."}
    ]
)

if response.stop_reason == "max_tokens": # Option 1: Increase max_tokens and retry # Option 2: Continue the conversation messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": "Please continue"}) continuation = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=2048, # Increased limit messages=messages )

3. tool_use — Tool Call Requested

What it means: Claude wants to use a tool. The response content will contain one or more tool_use blocks. How to handle it: Execute the requested tool(s), then send the results back to Claude.
if response.stop_reason == "tool_use":
    # Extract tool use blocks
    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 (your implementation)
            result = execute_tool(tool_name, tool_input)
            
            # Add tool result to messages
            messages.append({
                "role": "user",
                "content": [{
                    "type": "tool_result",
                    "tool_use_id": tool_use_id,
                    "content": str(result)
                }]
            })
    
    # Continue the conversation with tool results
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=messages
    )

4. stop_sequence — Custom Stop Sequence Triggered

What it means: Claude encountered a custom stop sequence you defined in your request. How to handle it: The response is complete per your custom criteria. Process it as needed.
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    stop_sequences=["\n\nHuman:"],  # Custom stop sequence
    messages=[
        {"role": "user", "content": "Tell me a story"}
    ]
)

if response.stop_reason == "stop_sequence": print(f"Stopped at sequence: {response.stop_sequence}") # Process the response up to the stop sequence

Handling Empty Responses with end_turn

A common pitfall: Claude sometimes returns an empty response (2-3 tokens with no content) with stop_reason: "end_turn". This typically happens in tool-use scenarios.

Why It Happens

Claude learns patterns from conversation history. If you consistently add text blocks immediately after tool results, Claude learns to expect that pattern and may end its turn prematurely.

How to Prevent It

Incorrect pattern: Adding text after tool_result
# DON'T DO THIS
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 pattern: Send tool results directly without additional text
# DO THIS INSTEAD
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 additional text blocks
    ]}
]

Fixing Empty Responses After They Occur

If you still get empty responses, don't just retry with the same messages—Claude has already decided it's done. Instead, add a continuation prompt:

def handle_empty_response(client, messages):
    response = client.messages.create(
        model="claude-opus-4-7",
        max_tokens=1024,
        messages=messages
    )
    
    # Check if response is empty
    if response.stop_reason == "end_turn" and not response.content:
        # ❌ Don't just retry with the empty response
        # ✅ 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 Handling Stop Reasons

1. Always Check stop_reason

Don't assume Claude will always respond with end_turn. Build logic that handles all possible stop reasons:

def handle_response(response, messages):
    if response.stop_reason == "end_turn":
        # Natural completion
        return response.content
    
    elif response.stop_reason == "max_tokens":
        # Token limit reached, continue
        messages.append({"role": "assistant", "content": response.content})
        messages.append({"role": "user", "content": "Please continue"})
        return handle_response(
            client.messages.create(model="claude-sonnet-4-20250514", max_tokens=2048, messages=messages),
            messages
        )
    
    elif response.stop_reason == "tool_use":
        # Execute tools and continue
        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)}]
                })
        return handle_response(
            client.messages.create(model="claude-sonnet-4-20250514", max_tokens=1024, messages=messages),
            messages
        )
    
    elif response.stop_reason == "stop_sequence":
        # Custom stop sequence triggered
        return response.content

2. Monitor Token Usage

Track usage.input_tokens and usage.output_tokens to optimize your max_tokens settings and manage costs.

3. Test with Different Scenarios

Create test cases that trigger each stop reason to ensure your handling logic works correctly:

  • Short prompts for end_turn
  • Long prompts that exceed max_tokens
  • Prompts that require tool use
  • Custom stop sequences

Key Takeaways

  • Always check stop_reason in every API response to determine the appropriate next action—don't assume end_turn.
  • Handle max_tokens gracefully by either increasing the limit or continuing the conversation with a "please continue" prompt.
  • For tool_use, execute the requested tools and return results in a new user message with tool_result blocks.
  • Prevent empty responses by never adding text blocks immediately after tool_result blocks in your message history.
  • Build recursive or loop-based handlers that can process multiple turns automatically, especially in tool-use scenarios where Claude may request multiple tools before completing.