BeClaude
Guide2026-05-05

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.

Quick Answer

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.

Claude APIstop_reasonMessages APItool useerror handling

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_reason value 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:

ValueMeaning
end_turnClaude finished naturally
tool_useClaude wants to call a tool
max_tokensOutput hit the token limit
stop_sequenceA 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_result blocks
  • You send Claude's completed response back without adding anything new
Why this happens: Claude learns patterns from the conversation history. If you consistently add text after tool results, Claude learns to expect that pattern and may end its turn prematurely.

#### How to Prevent Empty Responses

Incorrect approach: Adding text after tool results
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"}
        # ✅ 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_result block
  • 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_reason before 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_result blocks. This is the most common cause of empty responses.
  • Use continuation prompts for empty responses or truncated outputs.
  • Log stop_reason for debugging and monitoring.

Key Takeaways

  • stop_reason is not an error—it's a signal that tells you what to do next.
  • end_turn means Claude is done; display the response or wait for user input.
  • tool_use means Claude wants to call a tool; execute it and return results.
  • max_tokens and stop_sequence mean the response is truncated; continue the conversation.
  • Empty responses with end_turn are usually caused by adding text after tool_result blocks—fix the message structure, then use a continuation prompt to recover.