BeClaude
GuideBeginner2026-05-06

Mastering Claude’s Stop Reasons: Build Reliable API Applications

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

Quick Answer

This guide explains Claude’s stop_reason field—end_turn, tool_use, max_tokens, and stop_sequence—and shows how to handle each in your code. You’ll also learn to prevent and recover from empty responses when using tools.

Claude APIstop_reasonerror handlingtool useAPI best practices

Introduction

Every time you call the Claude Messages API, the response includes a stop_reason field. This small piece of data tells you why Claude stopped generating—whether it finished naturally, requested a tool call, hit a token limit, or encountered a custom stop sequence. Understanding these reasons is essential for building reliable, production-ready applications.

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

The stop_reason field appears in every successful API response. Unlike errors, which indicate a failure, stop_reason tells you why Claude completed its response normally.

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

This is the most common stop reason. It means Claude finished its response naturally—the model decided the assistant’s turn was complete.

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 wants to call a tool. The response will contain one or more tool_use content blocks. You must execute the tool and return the result in a new user message with a tool_result block.

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

max_tokens

Claude reached the max_tokens limit you set. The response may be cut off mid-sentence. You should continue the conversation to get the rest.

How to handle it:
if response.stop_reason == "max_tokens":
    # Append Claude's partial response
    messages.append({"role": "assistant", "content": response.content})
    # Ask Claude to continue
    messages.append({"role": "user", "content": "Please continue."})
    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 the request (e.g., "\n\nHuman:"). The stop_sequence field will contain the actual sequence that triggered the stop.

How to handle it:
if response.stop_reason == "stop_sequence":
    print(f"Stopped at sequence: {response.stop_sequence}")
    # Process the response up to that point

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.

Common Causes

  • Adding text 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 unchanged – Claude already decided it’s done, so it remains done.

How to Prevent Empty Responses

Incorrect: Adding text immediately after tool_result:
messages = [
    {"role": "user", "content": "Calculate 1234 + 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: Send only the tool result:
messages = [
    {"role": "user", "content": "Calculate 1234 + 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 result
    ]}
]

Recovering from Empty Responses

If you still get empty responses after fixing the above, 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-20250514",
        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"})
        response = client.messages.create(
            model="claude-opus-4-20250514",
            max_tokens=1024,
            messages=messages
        )
    
    return response

Best Practices for Handling Stop Reasons

  • Always check stop_reason – Don’t assume the response is complete. Check the reason before processing.
  • Use a loop for tool calls – Claude may call multiple tools in sequence. Keep the conversation going until you get end_turn.
  • Handle max_tokens gracefully – Always continue the conversation when you hit the token limit.
  • Avoid extra text after tool results – Stick to the tool_result block alone to prevent empty responses.
  • Log stop_sequence for debugging – If you use custom stop sequences, log which one triggered to understand Claude’s behavior.

Full Example: Tool-Using Agent Loop

Here’s a complete Python example that handles all stop reasons in a tool-using agent:

from anthropic import Anthropic

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

while True: response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=messages ) if response.stop_reason == "end_turn": # Final response print(response.content[0].text) break elif response.stop_reason == "tool_use": 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)} ]}) elif response.stop_reason == "max_tokens": messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": "Please continue."}) elif response.stop_reason == "stop_sequence": print(f"Hit stop sequence: {response.stop_sequence}") break

Conclusion

Understanding stop_reason is essential for building robust Claude API applications. By handling each reason correctly—especially tool_use and max_tokens—you can create seamless conversational experiences that handle tool calls, long responses, and edge cases like empty responses.

Key Takeaways

  • end_turn means Claude finished naturally; process the response as complete.
  • tool_use means Claude wants to call a tool; execute it and return the result.
  • max_tokens means you hit the token limit; continue the conversation to get the rest.
  • stop_sequence means a custom stop sequence was triggered; check the stop_sequence field.
  • Prevent empty responses by sending only tool_result blocks without extra text, and recover by adding a continuation prompt.