BeClaude
Guide2026-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 application. You'll also learn to prevent and recover from empty responses.

Claude APIstop_reasonMessages APItool_useerror handling

Introduction

When you send a request to Claude via the Messages API, the model doesn’t just return text—it also tells you why it stopped generating. This information lives in the stop_reason field of every successful response. Understanding these values is essential for building reliable, production-ready applications that can handle tool calls, incomplete responses, and edge cases gracefully.

In this guide, you’ll learn:

  • What each stop_reason value means
  • How to handle end_turn, tool_use, max_tokens, and stop_sequence in your code
  • How to prevent and recover from empty responses
  • Best practices for chaining multiple turns

The stop_reason Field

The stop_reason field is part of every successful Messages API response. Unlike HTTP errors (which indicate a failure to process your request), 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
  }
}

Stop Reason Values

end_turn

The most common stop reason. It means Claude finished its response naturally—it decided the assistant’s turn was complete. This is what you’ll see for most simple Q&A or conversational flows.

How to handle it:
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

Claude decided to call one or more tools. This is common in agentic workflows where Claude needs to fetch data, run calculations, or interact with external systems.

How to handle it:
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 and get result
            result = execute_tool(tool_name, tool_input)
            
            # Append tool result and continue the conversation
            messages.append({
                "role": "assistant",
                "content": response.content
            })
            messages.append({
                "role": "user",
                "content": [
                    {
                        "type": "tool_result",
                        "tool_use_id": tool_use_id,
                        "content": str(result)
                    }
                ]
            })
            
            # Send the follow-up request
            response = client.messages.create(
                model="claude-sonnet-4-20250514",
                max_tokens=1024,
                messages=messages
            )

max_tokens

Claude hit the max_tokens limit you set. The response is truncated—Claude had more to say but ran out of room.

How to handle it:
if response.stop_reason == "max_tokens":
    # The response is incomplete. Append Claude's partial response
    # and ask it to continue.
    messages.append({
        "role": "assistant",
        "content": response.content
    })
    messages.append({
        "role": "user",
        "content": "Please continue from where you left off."
    })
    
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=messages
    )

stop_sequence

Claude encountered a custom stop sequence you defined in your request (e.g., "`" or "END"). This is useful for structured outputs like code blocks or JSON.

How to handle it:
if response.stop_reason == "stop_sequence":
    # The response was cut off at the stop sequence.
    # You can extract the content before the sequence.
    full_text = response.content[0].text
    print(f"Response stopped at custom sequence: {full_text}")

Handling Empty Responses with end_turn

Sometimes Claude returns an empty response (exactly 2–3 tokens with no content) with stop_reason: "end_turn". This typically happens when Claude interprets that the assistant turn is complete, particularly after tool results.

Common Causes

  • Adding text blocks 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 will remain done.

How to Prevent Empty Responses

Incorrect approach:
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 approach:
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 block
    ]}
]

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-7",
        max_tokens=1024,
        messages=messages
    )
    
    # Check if response is empty
    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"})
        response = client.messages.create(
            model="claude-opus-4-7",
            max_tokens=1024,
            messages=messages
        )
    
    return response

Building a Robust Response Handler

Here’s a complete example that handles all stop reasons in a single loop:

from anthropic import Anthropic

client = Anthropic() messages = [{"role": "user", "content": "What is 15% of 200? Use the calculator tool."}]

while True: response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=messages ) if response.stop_reason == "end_turn": print("Final response:", response.content[0].text) break elif response.stop_reason == "tool_use": for block in response.content: if block.type == "tool_use": # Execute tool and append result result = execute_tool(block.name, block.input) messages.append({ "role": "assistant", "content": response.content }) messages.append({ "role": "user", "content": [{ "type": "tool_result", "tool_use_id": block.id, "content": str(result) }] }) elif response.stop_reason == "max_tokens": messages.append({ "role": "assistant", "content": response.content }) messages.append({ "role": "user", "content": "Please continue from where you left off." }) elif response.stop_reason == "stop_sequence": print("Response stopped at custom sequence:", response.content[0].text) break

Best Practices

  • Always check stop_reason – Don’t assume a response is complete just because you got a 200 status.
  • Handle tool_use in a loop – Claude may call multiple tools in sequence or in parallel.
  • Set max_tokens appropriately – For long-form content, use a higher limit or implement continuation logic.
  • Avoid extra text after tool_result – This prevents empty responses.
  • Use continuation prompts for truncated responses – A simple "Please continue" works well.

Key Takeaways

  • The stop_reason field tells you why Claude stopped: end_turn (natural finish), tool_use (tool call requested), max_tokens (truncated), or stop_sequence (custom stop).
  • For tool_use, append the tool result and continue the conversation—never add extra text after the result.
  • For max_tokens, append Claude’s partial response and ask it to continue.
  • Empty responses with end_turn can be prevented by sending only tool_result blocks (no extra text) and recovered with a continuation prompt.
  • Build a loop that checks stop_reason after every API call to handle multi-turn tool use and truncation gracefully.