BeClaude
GuideBeginnerAgents2026-05-19

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, including end_turn, tool_use, max_tokens, and stop_sequence scenarios with practical code examples.

Quick Answer

This guide explains Claude's stop_reason field values (end_turn, tool_use, max_tokens, stop_sequence) and how to handle each in your application logic, including preventing empty responses and managing tool call flows.

Messages APIstop_reasonerror handlingtool useresponse processing

Introduction

When building applications with Claude's Messages API, every successful response includes a stop_reason field that tells you why the model stopped generating. Understanding these values is essential for creating robust, production-ready applications that handle different scenarios appropriately.

Unlike error codes (which indicate something went wrong), stop_reason is part of every successful response. It's your application's signal for what to do next—whether that's displaying the response, continuing a conversation, or processing tool calls.

The stop_reason Field Explained

The stop_reason field appears in every successful Messages API response. 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
  }
}

There are four possible values for stop_reason, each requiring a different handling strategy.

Stop Reason Values

end_turn — Natural Completion

This is the most common stop reason. It means Claude finished its response naturally and is ready for the next user turn. 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": # Display the complete response 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-use scenarios when Claude interprets that the assistant's turn is complete.

Common causes:
  • Adding text blocks immediately after tool results in your message history
  • Sending Claude's completed response back without adding anything new
How to prevent empty responses:
# INCORRECT: 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 add text after tool_result
    ]}
]

CORRECT: 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 ]

If you still get empty responses after fixing the message structure:

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:
        # Add a continuation prompt in a NEW user message
        messages.append({
            "role": "user",
            "content": "Please continue with your response."
        })
        # Retry with the continuation prompt
        return client.messages.create(
            model="claude-opus-4-7",
            max_tokens=1024,
            messages=messages
        )
    return response

tool_use — Claude Wants to Use a Tool

When Claude decides to use a tool, it stops with stop_reason: "tool_use". Your application must:

  • Extract the tool call from the response content
  • Execute the tool
  • Return the result in a new user message with tool_result content blocks
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 location",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {"type": "string"}
            },
            "required": ["location"]
        }
    }],
    messages=[{"role": "user", "content": "What's the weather in London?"}]
)

if response.stop_reason == "tool_use": for block in response.content: if block.type == "tool_use": # Execute the tool tool_result = execute_tool(block.name, block.input) # Append the assistant's response and tool result messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": [ { "type": "tool_result", "tool_use_id": block.id, "content": str(tool_result) } ]}) # Continue the conversation response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, tools=tools, messages=messages )

max_tokens — Token Limit Reached

Claude stops when it reaches the max_tokens limit you set. The response may be cut off mid-sentence. Your application should:

  • Detect this condition
  • Optionally continue the conversation with a follow-up prompt
TypeScript example:
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic();

async function getCompleteResponse(messages: any[]) { let response = await client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 1024, messages: messages }); // If truncated, continue the conversation while (response.stop_reason === 'max_tokens') { messages.push({role: 'assistant', content: response.content}); messages.push({role: 'user', content: 'Please continue.'}); response = await client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 1024, messages: messages }); } return response; }

stop_sequence — Custom Stop Sequence Triggered

If you specified stop_sequences in your API request, Claude stops when it encounters one. The stop_sequence field in the response will contain the matched sequence.

Example:
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    stop_sequences=["\n\nHuman:"],
    messages=[{"role": "user", "content": "Tell me a short story"}]
)

if response.stop_reason == "stop_sequence": print(f"Stopped at sequence: {response.stop_sequence}") # Remove the stop sequence from the content if needed content = response.content[0].text if content.endswith(response.stop_sequence): content = content[:-len(response.stop_sequence)]

Building a Complete Handler

Here's a robust handler that manages all stop reasons:

from anthropic import Anthropic

class ClaudeResponseHandler: def __init__(self, client: Anthropic, model: str = "claude-sonnet-4-20250514"): self.client = client self.model = model self.messages = [] def process_response(self, response) -> str: """Process Claude's response and return the final text.""" if response.stop_reason == "end_turn": # Natural completion if response.content: return response.content[0].text else: # Handle empty response self.messages.append({"role": "user", "content": "Please continue."}) new_response = self.client.messages.create( model=self.model, max_tokens=1024, messages=self.messages ) return self.process_response(new_response) elif response.stop_reason == "tool_use": # Handle tool calls self.messages.append({"role": "assistant", "content": response.content}) for block in response.content: if block.type == "tool_use": result = self.execute_tool(block.name, block.input) self.messages.append({"role": "user", "content": [ {"type": "tool_result", "tool_use_id": block.id, "content": str(result)} ]}) # Continue the conversation new_response = self.client.messages.create( model=self.model, max_tokens=1024, tools=self.tools, messages=self.messages ) return self.process_response(new_response) elif response.stop_reason == "max_tokens": # Response was truncated self.messages.append({"role": "assistant", "content": response.content}) self.messages.append({"role": "user", "content": "Please continue."}) new_response = self.client.messages.create( model=self.model, max_tokens=1024, messages=self.messages ) return self.process_response(new_response) elif response.stop_reason == "stop_sequence": # Custom stop sequence triggered content = response.content[0].text if content.endswith(response.stop_sequence): content = content[:-len(response.stop_sequence)] return content return ""

Best Practices

  • Always check stop_reason — Never assume a response is complete without checking the stop reason.
  • Handle max_tokens gracefully — Implement continuation logic to ensure users get complete responses.
  • Structure tool results correctly — Send tool_result blocks without additional text to avoid empty responses.
  • Use continuation prompts for empty responses — Add a new user message with "Please continue" rather than retrying the same input.
  • Log stop reasons — Track which stop reasons occur in production to identify patterns and optimize your application.

Key Takeaways

  • Four stop reasons exist: end_turn (natural completion), tool_use (tool call requested), max_tokens (truncated), and stop_sequence (custom stop triggered).
  • Empty responses with end_turn occur when Claude thinks its turn is complete; fix by removing text after tool_result blocks or adding a continuation prompt.
  • Tool use requires multi-step handling: extract the tool call, execute it, and return the result in a new user message.
  • max_tokens truncation is common with long responses; implement a retry loop with a "Please continue" prompt to get the full response.
  • Always check stop_reason in your application logic to build robust, production-ready Claude integrations.