[MODEL] Haiku 4.5 wrong behaviour tool usage
Preflight Checklist
- [x] I have searched existing issues for similar behavior reports
- [x] This report does NOT contain sensitive information (API keys, passwords, etc.)
Type of Behavior Issue
Other unexpected behavior
What You Asked Claude to Do
When operating in an agentic manner with a set of tools, Haiku 4.5 can enter a repetitive loop, calling the same tool for an action it has already completed. This occurs even when the tool provides explicit feedback that the action is redundant and that the model should proceed with a different action.
The model fails to correctly process the tool's output, update its internal plan, and move on to the next logical step. This leads to stalled tasks, wasted computation, and a failure to complete the user's objective.
What Claude Actually Did
We have observed this behaviour in a complex Terraform coding agent. After successfully calling the read_file tool, the agent's state received the following tool result, explicitly telling it to move on:
{
"tool_call_id": "toolu_vrtx_01RTb5NSYCNJYj5uKEZzKw9z",
"tool_name": "read_file",
"result": "The output of this tool has already been provided. Please, use a different tool."
}
Despite this clear instruction in the tool's output, the model's next action was to call read_file on the exact same file again, leading to an infinite loop.
Expected Behavior
The model should follow a logical sequence:
- Turn 1: Call
read_filewith path='config.txt'. - Receive the file content ("INITIAL_CONFIG=true\n").
- Turn 2: Process the content and recognize the next step is to write. Call
write_filewithpath='config.txt'andcontent='INITIAL_CONFIG=true\nSTATUS=updated'. - Receive confirmation that the write was successful.
- Turn 3: Call
task_complete as the request is fulfilled.
Files Affected
Permission Mode
I don't know / Not sure
Can You Reproduce This?
Yes, every time with the same prompt
Steps to Reproduce
The following Python script provides a minimal, reproducible example that simulates this agentic looping behavior. We define a read_file tool that keeps track of which files have already been read.
It is a minimal example designed to demonstrate the failure pattern. Due to the simplicity of the task and the stochastic nature of LLMs, the model may occasionally succeed and proceed to the write_file step correctly.
from anthropic import Anthropic
client = Anthropic()
MODEL_NAME = "claude-haiku-4-5-20251001"
tools = [
{
"name": "read_file",
"description": "Read the contents of a file at a given path.",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "The path to the file."},
},
"required": ["path"],
},
},
{
"name": "write_file",
"description": "Write content to a file at a given path. Overwrites the file if it exists.",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "The path to the file."},
"content": {"type": "string", "description": "The content to write to the file."},
},
"required": ["path", "content"],
},
},
{
"name": "task_complete",
"description": "Call this function when the user's request has been successfully completed.",
"input_schema": {
"type": "object",
"properties": {
"summary": {"type": "string", "description": "A brief summary of the work done."},
},
"required": ["summary"],
},
},
]
messages = [
{"role": "user", "content": "Please read the file 'config.txt' and then add a new line to it that says 'STATUS=updated'."}
]
print(f"User: {messages[0]['content']}\n")
# Simulate the conversation loop with state tracking
max_turns = 5
files_already_read = set() # Our state tracker
for i in range(max_turns):
print(f"--- Turn {i+1} ---")
response = client.messages.create(
model=MODEL_NAME,
max_tokens=2048,
messages=messages,
tools=tools,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
final_message = response.content[0].text
print(f"\nModel stopped without tool use: {final_message}")
break
tool_calls = [block for block in response.content if block.type == "tool_use"]
if not tool_calls:
print("\nModel stopped but no tool calls were found.")
break
# Process all tool calls from the model's turn
for tool_call in tool_calls:
tool_name = tool_call.name
tool_input = tool_call.input
tool_call_id = tool_call.id
print(f"Model wants to call tool: {tool_name} with input: {tool_input}")
tool_result_content = ""
if tool_name == "read_file":
file_path = tool_input.get("path")
if file_path in files_already_read:
# This is the crucial feedback the model ignores
tool_result_content = f"Error: The content of '{file_path}' has already been provided. Please use a different tool to make progress."
else:
tool_result_content = "INITIAL_CONFIG=true\n"
files_already_read.add(file_path) # Update our state
elif tool_name == "write_file":
tool_result_content = f"Successfully wrote to file: {tool_input.get('path')}"
elif tool_name == "task_complete":
print(f"\nTask marked as complete. Summary: {tool_input.get('summary')}")
# End the loop if task is complete
exit()
messages.append({
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_call_id,
"content": tool_result_content,
}
]
})
print(f"Providing tool result: '{tool_result_content}'")
else:
print("\nLoop limit reached. The model is likely stuck.")
Claude Model
claude-haiku-4-5-20251001
Relevant Conversation
Impact
Critical - Data loss or corrupted project
Claude Code Version
2.0.24
Platform
Anthropic API
Additional Context
This seems to happen more often with long conversations
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗