handling-stop-reasons
build-with-claude/handling-stop-reasons
History
build-with-claude/handling-stop-reasons Changed · +1 / -2 lines
<CodeGroup exclude="shell:cURL"> ```bash CLI - RESPONSE=$(ant messages create --max-tokens 1024 \ - --format jsonl < request.yaml) + RESPONSE=$(ant messages create --max-tokens 1024 --format jsonl < request.yaml) # Check if the response was truncated mid tool use STOP_REASON=$(jq -r '.stop_reason' <<<"$RESPONSE")
build-with-claude/handling-stop-reasons First recorded · 3677 lines, first recorded
## Quick reference ## The stop\_reason field ## Stop reason values ### end\_turn ### max\_tokens ### stop\_sequence ### tool\_use ### pause\_turn ### refusal ### model\_context\_window\_exceeded ## Best practices for handling stop reasons ### Always check stop\_reason ### Handle truncated responses gracefully ### Implement retry logic for pause\_turn ## Stop reasons vs. errors ### Stop reasons (successful responses) ### Errors (failed requests) ## Streaming considerations ## Common patterns ### Handling tool use workflows ### Ensuring complete responses ### Getting maximum tokens without knowing input size ## Next steps
The first capture of this source. The page was already there, and this is what it said.
---
title: Stop reasons and fallback
url: https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons
description: Learn what each stop_reason value means and how to handle truncation, tool use, paused turns, and refusals in your application.
---
Every Messages API response includes a `stop_reason` field that tells you why Claude stopped generating. Check this field to decide whether to use the response as-is, continue the conversation, retry, or fall back to another model.
For the full response schema, see the [Messages API reference](https://platform.claude.com/docs/en/api/messages/create).
## Quick reference
| Value | When it occurs | What to do |
| -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`end_turn`](https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#end-turn) | Claude finished its response naturally. | Use the response. |
| [`max_tokens`](https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#max-tokens) | The response reached your `max_tokens` limit. | Raise `max_tokens` or [continue the response](https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#ensuring-complete-responses). |
| [`stop_sequence`](https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#stop-sequence) | Claude emitted one of your `stop_sequences`. | Read `stop_sequence` to see which one fired. |
| [`tool_use`](https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#tool-use) | Claude is calling a tool. | Run the tool and return the result. A server tool call still missing its result block completes in a later response. |
| [`pause_turn`](https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#pause-turn) | A server-tool loop reached its iteration limit. | Send the assistant content back to continue. |
| [`refusal`](https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#refusal) | Claude declined to respond. | Read `stop_details` and [retry on a fallback model](https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback). |
| [`model_context_window_exceeded`](https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#model-context-window-exceeded) | The response filled the model's context window. | Treat the response as truncated. |
## The stop\_reason field
The `stop_reason` field is part of every successful Messages API response. Unlike errors, which indicate failures in processing your request, `stop_reason` tells you why Claude completed its response generation.
```json Example 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,
"stop_details": null,
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}
```
## Stop reason values
### end\_turn
The most common stop reason. Indicates Claude finished its response naturally.
<CodeGroup>
```bash cURL
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello!"}]
}' | jq 'if .stop_reason == "end_turn" then (.content[] | select(.type == "text") | .text) else . end'
```
```bash CLI
ant messages create \
--model claude-opus-5 \
--max-tokens 1024 \
--message '{role: user, content: "Hello!"}' \
--format json | jq 'if .stop_reason == "end_turn" then (.content[] | select(.type == "text") | .text) else . end'
```
```python Python
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
if response.stop_reason == "end_turn":
# Process the complete response
for block in response.content:
if block.type == "text":
print(block.text)
```
```typescript TypeScript
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }]
});
if (response.stop_reason === "end_turn") {
// Process the complete response
const textBlock = response.content.find(
(block): block is Anthropic.TextBlock => block.type === "text"
);
console.log(textBlock?.text);
}
```
```csharp C#
AnthropicClient client = new();
var response = await client.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus5,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello!" }]
});
if (response.StopReason == "end_turn")
{
// Process the complete response
foreach (var block in response.Content)
{
if (block.TryPickText(out var textBlock))
{
Console.WriteLine(textBlock.Text);
}
}
}
```
```go Go
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello!")),
},
})
if err != nil {
log.Fatal(err)
}
if response.StopReason == "end_turn" {
// Process the complete response
for _, block := range response.Content {
if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
fmt.Println(textBlock.Text)
}
}
}
```
```java Java
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
Message response = client.messages().create(
MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5)
.maxTokens(1024L)
.addUserMessage("Hello!")
.build()
);
if (response.stopReason().map(StopReason.END_TURN::equals).orElse(false)) {
// Process the complete response
response.content().stream()
.flatMap(block -> block.text().stream())
.forEach(textBlock -> IO.println(textBlock.text()));
}
```
```php PHP
$client = new Client();
$response = $client->messages->create(
maxTokens: 1024,
messages: [['role' => 'user', 'content' => 'Hello!']],
model: 'claude-opus-5',
);
if ($response->stopReason === 'end_turn') {
// Process the complete response
foreach ($response->content as $block) {
if ($block->type === 'text') {
echo $block->text, PHP_EOL;
}
}
}
```
```ruby Ruby
client = Anthropic::Client.new
response = client.messages.create(
model: "claude-opus-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }]
)
if response.stop_reason == :end_turn
# Process the complete response
response.content.each do |block|
puts block.text if block.type == :text
end
end
```
</CodeGroup>
<Accordion title="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 occurs 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 determined it's done, so it will remain done)
**How to prevent empty responses:**
<CodeGroup exclude="shell">
```python Python
# 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
]
```
```typescript TypeScript
// INCORRECT: Adding text immediately after tool_result
let messages: Anthropic.MessageParam[] = [
{ 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",
Cut at 300 lines.