Your agent loop exits whenever Claude's reply contains text, and users report tasks stopping halfway through. What should control the loop?
Why C is the answer
Continue while stop_reason is "tool_use" and finish on "end_turn"
stop_reason is the signal built for this. Text can appear alongside a tool call, so reading the text is unreliable, and an iteration cap is a safety net rather than a stopping rule.
In detail
- Your code owns the agent loop. After each response it has to decide whether to run tools and call Claude again, or stop.
- The API tells you directly through stop_reason. When Claude wants a client tool, it stops with tool_use and your code runs the operation and sends back a tool_result.
- A single response can hold text and a tool_use block together. Treating text as "finished" ends the loop while work is still pending, which is the bug in this question.
- Other values matter too. max_tokens means the reply was cut off, pause_turn means a server-tool loop needs you to send the content back, and refusal needs its own handling.
- Log the stop reason on every turn, so a stalled task shows why it ended.
Use case
An illustrative scenario, not a real incident.
A support agent replies "Let me check your order" and requests a lookup tool in the same response. A loop that stops at the first text ends there, so the customer never gets the order status. Reading stop_reason keeps it running until the answer is ready.
See it in different ways
flowchart TD
A["Call Claude"] --> B{"stop_reason"}
B -->|"tool_use"| C["Run tools, send results"]
C --> A
B -->|"end_turn"| D["Finish"]sequenceDiagram participant App as Your code participant C as Claude participant T as Tool App->>C: Messages and tool definitions C-->>App: stop_reason tool_use App->>T: Run the tool T-->>App: Result App->>C: tool_result C-->>App: stop_reason end_turn
stateDiagram-v2 [*] --> Calling Calling --> RunningTools: tool_use RunningTools --> Calling: send tool_result Calling --> Finished: end_turn Calling --> CutOff: max_tokens Finished --> [*] CutOff --> [*]
flowchart TD
Q{"What is stop_reason?"}
Q -->|"tool_use"| A0["Run the tools and call again"]
Q -->|"end_turn"| A1["Finish the task"]
Q -->|"max_tokens"| A2["Reply was cut off, so handle it"]Key point: Drive the loop from stop_reason, never from the words in the reply.
Why the other options are wrong
- A. Exit when the reply contains the word "done" or a similar completion phrase
- Searching for words is fragile. The model might never use the phrase, or might use it while still planning more work.
- B. Run a fixed five iterations on every task, then return whatever you have
- A fixed count stops some tasks early and wastes turns on others. A turn limit is a good safety net, but it should not be the normal way to finish.
- D. Ask the model at the end of each turn whether it believes it has finished
- This adds a model call to every turn and returns an opinion, when the API already gives you a precise signal.

