CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/API & SDK
API & SDK/2026-05-16Intermediate

Debugging Claude API Tool Use Schema Errors: 3 Patterns I've Hit and How to Fix Them

A practical guide to diagnosing Claude API Tool Use errors—from schema definition mistakes to invalid_tool_use blocks and Claude ignoring your tools entirely. Based on real implementation experience.

tool-use22api38troubleshooting87python22error-handling11

When I first integrated Tool Use into Beautiful HD Wallpapers—an app I've been developing since 2014 that now has over 50 million cumulative downloads—the error I hit wasn't what I expected. The schema looked fine on the surface. Claude wasn't throwing an obvious error. But the tool_result I was returning had a subtle structural mismatch with what the API expected.

The frustrating part: it all came back as a generic invalid_request_error. No line number, no field name, just a cryptic message that could mean a dozen different things. I spent more time on that one issue than I care to admit.

If you're implementing Tool Use and something's not working, here's the diagnostic flow I now use every time.

Understanding the 3-Phase Structure First

Tool Use involves three distinct phases, and knowing which phase your error occurs in narrows down the cause immediately.

Phase 1: Define tools — You pass a tools array to the API with your tool schemas. Errors here mean the schema structure is malformed. Phase 2: Claude calls a tool — The response contains a tool_use block with Claude's chosen arguments. Errors here are rare unless you've constructed unusual inputs. Phase 3: Return the result — You add a user turn containing a tool_result block. Errors here usually mean a structural mismatch or a missing tool_use_id.

When an invalid_request_error comes back immediately after your first call, that's Phase 1. If it happens after you've sent the tool result, that's Phase 3. If there's no error but Claude keeps responding with text instead of calling a tool, that's a description or prompt design issue.

import anthropic
 
client = anthropic.Anthropic()
 
# Phase 1: define tools with correct schema
tools = [
    {
        "name": "classify_image",
        "description": "Classify an image into one of the given categories. Call this when the user asks to categorize or classify an image.",
        "input_schema": {
            "type": "object",
            "properties": {
                "image_url": {
                    "type": "string",
                    "description": "URL of the image to classify"
                },
                "categories": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "List of candidate categories"
                }
            },
            "required": ["image_url", "categories"]
        }
    }
]
 
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "Please classify this image: https://example.com/photo.jpg"}]
)

Pattern 1: Malformed input_schema

The most common cause is a required field placed inside a property definition instead of at the schema's top level. It's an easy mistake because some validation libraries accept it, but the Claude API follows JSON Schema Draft 7 strictly.

The required field must be a top-level array of property name strings, sibling to properties. Writing "required": true inside a property definition either silently fails or causes an invalid_request_error. Another common issue is omitting type from property definitions entirely. Every property needs an explicit type, even if you're only providing a description.

# ❌ Wrong: required inside the property definition
"input_schema": {
    "type": "object",
    "properties": {
        "query": {
            "type": "string",
            "required": True  # this field is ignored or causes an error
        }
    }
}
 
# ✅ Correct: required as a top-level array of field name strings
"input_schema": {
    "type": "object",
    "properties": {
        "query": {
            "type": "string",
            "description": "The search query"
        }
    },
    "required": ["query"]
}

A quick validation step before sending to the API can catch this immediately. The jsonschema package's Draft7Validator.check_schema() validates the schema structure locally without making any API calls.

Pattern 2: Incorrect tool_result structure

After Claude returns a tool_use block, the result must come back in a specific format as part of a user message. A plain text string won't work — it needs to be a tool_result block inside a content array.

The tool_use_id must exactly match the ID from Claude's tool_use block in the previous response. If you're handling parallel tool calls — where Claude calls multiple tools in one turn — each result needs its own tool_result block with the correct matching ID. Using the wrong ID, or omitting it, causes an invalid_request_error at Phase 3.

# ❌ Wrong: returning a plain text message instead of tool_result
messages.append({
    "role": "user",
    "content": "The classification result is: landscape"
})
 
# ✅ Correct: tool_result block inside a content array
if response.stop_reason == "tool_use":
    tool_use_block = next(b for b in response.content if b.type == "tool_use")
    result = execute_my_tool(tool_use_block.input)
 
    messages.append({
        "role": "user",
        "content": [
            {
                "type": "tool_result",
                "tool_use_id": tool_use_block.id,  # copy exactly from Claude's response
                "content": str(result)
            }
        ]
    })

For parallel tool calls, you need to collect all results and send them together in a single user message, not as separate messages.

Pattern 3: Claude ignores the tool entirely

If there's no error but Claude keeps responding with text, the problem is almost always in the description field or the user's message.

The description is the primary signal Claude uses to decide whether to call a tool. Vague descriptions like "processes data" or "handles requests" leave Claude uncertain, and it defaults to a text response when the task seems answerable without a tool.

Write descriptions that specify the trigger condition explicitly. "Call this tool when the user asks to X" or "Use this when you need Y" gives Claude a clear decision rule. If you want to guarantee tool use regardless of the prompt, tool_choice lets you force it.

# Force at least one tool to be called
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "any"},  # Claude must call at least one tool
    messages=[{"role": "user", "content": "Classify this image"}]
)
 
# Force a specific tool
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "tool", "name": "classify_image"},
    messages=[{"role": "user", "content": "Analyze this image"}]
)

Using type: "any" is a good diagnostic step: if it works with forced tool use but not with type: "auto", the issue is in how Claude is interpreting the trigger conditions in your description and prompt.

Additional Schema Design Notes

A few smaller issues that tend to surface in production after the initial implementation works.

enum casing — When you use "enum" to restrict string values, Claude will pick from your list, but the case of the output string can sometimes differ from your definitions. Normalizing to lowercase on your side before processing is safer than assuming exact case matches.

Nested objects — Every level of nested objects needs its own "type": "object" declaration along with its own properties. It's repetitive, but the structure can't be abbreviated. Missing type at any nesting level causes validation failures.

Description length — Descriptions that are too long increase token usage. A two-to-three sentence description covering "what the tool does" and "when to call it" is the practical sweet spot. With multiple tools defined, long descriptions across all of them add up quickly in context costs.

additionalProperties: false — Adding this to your schema prevents Claude from including fields you didn't define. It's useful for strict output validation, but it also means if Claude tries to add any extra field, the tool call itself fails. Start without it and add it once your tool definitions are stable.

Putting It Together

The pattern I follow now when something breaks: check which phase the error is in, then go down the checklist for that phase. Phase 1 errors → look at schema structure. Phase 3 errors → look at tool_result format and ID matching. No error but no tool call → fix the description or use tool_choice to diagnose.

Once you've built this mental model, most Tool Use errors resolve in under ten minutes. The implementation itself is straightforward once you get past the initial schema learning curve.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API & SDK2026-05-06
Claude API × Python in Practice: Building an AI Assistant with Tool Calling and Streaming
A practical guide to combining Claude API's Tool Use and Streaming in Python. Build a working AI assistant with real tool execution, complete source code included, plus a breakdown of the tricky parts that trip up most developers.
API & SDK2026-05-05
Let Claude Diagnose Its Own Tool Errors — Building a Self-Correction Loop with the Anthropic API
Learn how to handle Tool Use failures gracefully by feeding error details back to Claude using the is_error flag, enabling self-diagnosis and automatic retry. Includes working Python code and production antipatterns to avoid.
API & SDK2026-04-30
Fix Claude API's 'messages.X.role must alternate' in One Minute — Common 400 invalid_request_error Patterns
A pattern-by-pattern guide to fixing the 'messages.X.role must alternate' error in Claude's Messages API — covering user/assistant alternation, tool_use and tool_result pairing, and history-trimming pitfalls with working code.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →