BeClaude
GuideBeginnerAgents2026-05-12

Mastering Claude API Stop Reasons: Build Robust Applications with end_turn, max_tokens & tool_use

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

Quick Answer

This guide explains Claude API's stop_reason field—end_turn, max_tokens, tool_use, and stop_sequence—with practical code examples and strategies to handle empty responses, tool call loops, and truncated output.

stop_reasonMessages APIerror handlingtool useClaude API

Mastering Claude API Stop Reasons: Build Robust Applications

Every time you call the Claude API, the response includes a stop_reason field. This small piece of data tells you why the model stopped generating—whether it finished naturally, hit a token limit, requested a tool call, or encountered a stop sequence. Understanding and handling these reasons correctly 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 your code (Python & TypeScript)
  • How to prevent and recover from empty responses
  • Best practices for tool-using agents and streaming

What is stop_reason?

The stop_reason field appears in every successful Messages API response. It is not an error—it’s a signal about why Claude finished its response generation. 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 Four Stop Reason Values

1. end_turn — Natural Completion

This is the most common stop reason. Claude finished its response naturally and expects no further action. 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": print(response.content[0].text)

TypeScript example:
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic(); 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); }

2. max_tokens — Token Limit Reached

Claude stopped because it hit the max_tokens limit you set. The response may be truncated—the model had more to say but was cut off.

What to do:
  • Increase max_tokens if you need longer responses.
  • Or, send a follow-up message like "Please continue" to let Claude finish.
if response.stop_reason == "max_tokens":
    # Option 1: Increase max_tokens and retry
    # Option 2: Ask Claude to continue
    messages.append({"role": "user", "content": "Please continue"})
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=2048,
        messages=messages
    )

3. tool_use — Claude Wants to Call a Tool

When you provide tools to Claude, it may stop to request a tool call. The response content will contain one or more tool_use blocks.

Critical: You must execute the tool and return the result in a tool_result block. Do not add extra text after the tool_result—that can cause empty responses (see below).
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)
                }]
            })
    # Now call Claude again with the tool result
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=messages
    )

4. stop_sequence — Custom Stop Sequence Hit

If you provided custom stop_sequences in your API request, Claude will stop when it encounters one. The stop_sequence field will contain the exact sequence that triggered the stop.

if response.stop_reason == "stop_sequence":
    print(f"Stopped at sequence: {response.stop_sequence}")
    # Handle accordingly

Handling Empty Responses with end_turn

A common pitfall: Claude returns an empty response (2–3 tokens, no content) with stop_reason: "end_turn". This usually happens in tool-use workflows.

Why it happens

  • Adding text after tool_result: Claude learns that the user always inserts 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 stays done.

How to prevent it

Incorrect — adding text 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 an empty response, do not retry with the same messages—Claude will remain stuck. Instead, add a new user message asking it to continue:

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
        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 Production

1. Always check stop_reason

Don’t assume end_turn. Build a switch or if-else chain to handle each case:

match response.stop_reason:
    case "end_turn":
        display_response(response)
    case "max_tokens":
        request_continuation(response)
    case "tool_use":
        execute_tools_and_continue(response)
    case "stop_sequence":
        handle_custom_stop(response)

2. Handle streaming responses

In streaming mode, the final message will contain the stop_reason. Accumulate content and check at the end:

with client.messages.stream(...) as stream:
    for event in stream:
        if event.type == "message_stop":
            stop_reason = event.message.stop_reason
            # Handle accordingly

3. Set appropriate max_tokens

For open-ended tasks, use a generous limit (e.g., 4096). For structured outputs, set a lower limit to avoid wasted tokens.

4. Log stop reasons for debugging

Track stop_reason in your logs to identify patterns—like frequent max_tokens stops indicating you need higher limits.

Summary of Stop Reasons

stop_reasonMeaningAction Required
end_turnNatural completionDisplay response
max_tokensToken limit hit, response truncatedIncrease limit or ask to continue
tool_useClaude wants to call a toolExecute tool, return result
stop_sequenceCustom stop sequence encounteredHandle based on your logic

Key Takeaways

  • Always check stop_reason in your API responses—it tells you exactly why Claude stopped and what to do next.
  • Never add text after tool_result blocks; it causes empty responses. Send only the tool result.
  • Recover from empty responses by adding a new user message like "Please continue"—don’t retry the same messages.
  • For max_tokens stops, either increase the limit or ask Claude to continue; the response is truncated.
  • Log stop reasons in production to monitor behavior and optimize your API usage.