Claw Mart
← Back to Blog
March 21, 20269 min readClaw Mart Team

Tool Call Failures in OpenClaw: Diagnosis & Solutions

Tool Call Failures in OpenClaw: Diagnosis & Solutions

Tool Call Failures in OpenClaw: Diagnosis & Solutions

If you've been building with OpenClaw for more than a week, you've hit this wall. You set up your agent, define your tools, write what you think is a clean schema, hit run β€” and the whole thing faceplants. Maybe you get a JSONDecodeError. Maybe the model hallucinates a parameter that doesn't exist. Maybe it just… stops calling tools entirely and starts monologuing like a philosophy major who just discovered Camus.

You're not alone. Tool call failures are the single most common reason people ragequit agent development. And the frustrating part is that most of these failures are predictable, diagnosable, and fixable β€” once you know what you're looking at.

This post is a full breakdown of why tool calls fail in OpenClaw, how to diagnose each failure type, and exactly what to do about it. No hand-waving, no "just prompt engineer harder." Actual solutions.


The Anatomy of a Tool Call

Before we get into failures, let's make sure we're on the same page about what's actually happening when OpenClaw executes a tool call.

When your agent decides it needs to use a tool, three things happen in sequence:

  1. Tool Selection β€” The model looks at the available tools and picks one (or more).
  2. Argument Generation β€” The model constructs the arguments to pass to that tool based on the schema you defined.
  3. Execution & Response Handling β€” OpenClaw executes the tool, gets a result, and feeds it back into the agent's context.

Failures can happen at any of these three stages, and each one has different root causes and different fixes. Most people treat all tool call failures the same ("the model is broken") and try to fix them with prompt changes. That works maybe 30% of the time. Let's do better.


Failure Type #1: Output Format & Parsing Errors

What it looks like:

OutputParserError: Failed to parse tool call output
JSONDecodeError: Expecting property name enclosed in double quotes: line 3 column 5

Or you'll see the model output something like:

Sure! I'll search for that information now.

{"tool": "search_database", "args": {"query": "revenue Q3 2026"}}

That extra text before the JSON? That kills your parser. The model is being "helpful" by explaining what it's about to do, and in doing so, it breaks everything.

Why it happens:

The model is trained on conversational data where explaining your reasoning is the right thing to do. Without strong constraints, it defaults to chatty behavior. This is especially brutal with open-source models running through Ollama or vLLM, but it happens with proprietary models too β€” more often than OpenAI's marketing would have you believe.

How to fix it in OpenClaw:

First, make sure you're using OpenClaw's structured output mode. This isn't optional β€” it's the difference between a 65% success rate and a 95% one.

from openclaw import Agent, ToolSchema
from openclaw.output import StructuredOutputHandler

agent = Agent(
    model="your-model-here",
    output_handler=StructuredOutputHandler(
        strict_json=True,
        strip_preamble=True,  # Removes any text before the JSON
        max_retries=2          # Auto-retries on parse failure
    )
)

The strip_preamble flag is doing heavy lifting here. It uses a regex + token-level detection to find the actual JSON blob in the model's output, even when the model wraps it in markdown code fences or adds explanatory text.

Second, if you're using a local model, enable constrained generation:

from openclaw.constraints import JsonConstraint

agent = Agent(
    model="llama3.1-70b",
    constraints=[JsonConstraint(schema=your_tool_schema)]
)

This forces the model to only output valid tokens that conform to your JSON schema during generation. It's slower β€” maybe 10-15% more latency β€” but it eliminates parsing failures almost entirely. Worth it.


Failure Type #2: Argument Hallucination & Schema Violations

This one is insidious because it doesn't always throw an error. Sometimes the model generates perfectly valid JSON that passes parsing… but the values are wrong.

What it looks like:

You define a tool like this:

@tool(
    name="get_customer_orders",
    description="Retrieve orders for a customer by their ID",
    params={
        "customer_id": {"type": "integer", "required": True},
        "status_filter": {"type": "string", "enum": ["pending", "shipped", "delivered"]},
        "limit": {"type": "integer", "default": 10, "max": 100}
    }
)
def get_customer_orders(customer_id: int, status_filter: str = None, limit: int = 10):
    # ... your implementation

And the model calls it with:

{
    "customer_id": "cust_12345",
    "status": "active",
    "max_results": 50
}

Three problems: customer_id is a string instead of an integer, status doesn't exist (the field is status_filter), and max_results is a hallucinated parameter name.

Why it happens:

The model is pattern-matching against similar APIs it saw in training data. It "knows" that customer APIs often have a status field and a max_results parameter, so it fills in what feels right rather than what your schema actually says. This gets worse as you add more tools β€” the model starts confusing schemas across tools.

How to fix it:

OpenClaw has a built-in validation layer that you should absolutely be using:

from openclaw.validation import SchemaValidator

validator = SchemaValidator(
    strict_types=True,       # Reject type mismatches instead of coercing
    reject_unknown=True,     # Reject parameters not in the schema
    auto_correct=True        # Attempt to fix minor issues before rejecting
)

agent = Agent(
    model="your-model-here",
    tool_validator=validator
)

The auto_correct flag is interesting β€” it handles cases where the model sends "customer_id": "12345" (string instead of int) by attempting a safe type coercion. It won't turn "hello" into an integer, but it will turn "12345" into 12345. This alone fixes maybe 20% of schema violations in practice.

But the bigger fix is writing better tool descriptions. I know, I know β€” "just prompt better" is what I said we wouldn't do. But this isn't vague prompt engineering. It's specific:

@tool(
    name="get_customer_orders",
    description=(
        "Retrieve orders for a customer. "
        "IMPORTANT: customer_id must be a numeric integer, not a string. "
        "Use status_filter (not 'status') to filter by order state. "
        "Only accepted values: pending, shipped, delivered."
    ),
    params={...}
)

Yes, you're literally telling the model "don't do the dumb thing." It feels ridiculous. It works. The models are responsive to explicit warnings about common mistakes, especially when those warnings are in the tool description rather than the system prompt.


Failure Type #3: Wrong Tool Selection

What it looks like:

You have 12 tools registered. The user asks "What's the weather in Austin?" and instead of calling get_weather, the model calls search_knowledge_base with {"query": "weather Austin"}.

Why it happens:

Two reasons. First, with more than about 8-10 tools, models start to degrade in selection accuracy. The tool descriptions compete for attention in the context window, and the model gets confused. Second, if your tool descriptions are too similar or too vague, the model genuinely can't tell which one is right.

How to fix it:

OpenClaw supports tool namespacing and dynamic tool loading, and you should use both:

from openclaw import Agent, ToolGroup

# Group related tools
weather_tools = ToolGroup(
    name="weather",
    tools=[get_weather, get_forecast, get_weather_alerts],
    trigger_keywords=["weather", "temperature", "forecast", "rain", "snow"]
)

database_tools = ToolGroup(
    name="data",
    tools=[search_knowledge_base, get_customer_orders, run_sql_query],
    trigger_keywords=["search", "find", "query", "customer", "order", "database"]
)

agent = Agent(
    model="your-model-here",
    tool_groups=[weather_tools, database_tools],
    dynamic_loading=True  # Only inject relevant tool groups per turn
)

With dynamic_loading=True, OpenClaw does a lightweight pre-classification of the user's message and only injects the relevant tool group(s) into the model's context. Instead of seeing all 12 tools, the model might only see 3. This dramatically improves selection accuracy and reduces context window usage.

If you don't want dynamic loading (maybe your use case needs all tools available always), at minimum make your tool names and descriptions maximally distinct. Don't have search_database and query_database β€” the model will confuse them. Name them search_kb_by_text and run_raw_sql_query. Be aggressively specific.


Failure Type #4: Error Recovery Collapse

This is the one that makes people give up on agents entirely.

What it looks like:

The agent calls a tool. The tool returns an error (API rate limit, invalid input that passed validation, network timeout, whatever). Instead of adjusting and trying again, the agent either:

  • Repeats the exact same call in an infinite loop
  • Gives up and tells the user "I encountered an error" without trying anything else
  • Starts hallucinating results instead of actually calling the tool

Why it happens:

Most agent loops don't have good error handling built into the reasoning cycle. The model sees "error" in the tool response and doesn't know what to do with it because it has no strategy for recovery.

How to fix it:

OpenClaw's retry system is one of its strongest features. Use it:

from openclaw.recovery import RetryPolicy, ErrorStrategy

policy = RetryPolicy(
    max_retries=3,
    strategies={
        "validation_error": ErrorStrategy.SELF_CORRECT,  # Feed error back to model to fix args
        "rate_limit": ErrorStrategy.EXPONENTIAL_BACKOFF,  # Wait and retry with same args
        "api_error": ErrorStrategy.FALLBACK_TOOL,         # Try alternative tool if available
        "timeout": ErrorStrategy.RETRY_ONCE,              # Simple retry
    },
    give_up_message="Explain to the user what went wrong and what they can try instead"
)

agent = Agent(
    model="your-model-here",
    retry_policy=policy
)

The SELF_CORRECT strategy is the magic one. When a tool call fails validation, OpenClaw feeds the error message back to the model with a specific prompt: "Your previous tool call failed with this error: [error]. Here's the tool schema again. Generate a corrected call." This works shockingly well β€” in my testing, about 80% of validation errors self-correct on the first retry.

The FALLBACK_TOOL strategy lets you define backup tools. If your primary weather API is down, maybe fall back to a cached version or a different provider. You configure this at the tool group level:

get_weather.set_fallback(get_weather_cached)

Simple, but it prevents the complete collapse that users experience in other frameworks.


Failure Type #5: The Debugging Black Hole

This isn't a tool call failure per se β€” it's a failure of you to figure out why the tool call failed.

What it looks like:

"It's not working and I have no idea why."

You stare at your terminal. The agent did something wrong. Was it the prompt? The schema? The model? The tool implementation? You have no visibility into the decision-making process.

How to fix it:

Turn on OpenClaw's trace mode:

from openclaw.debug import Tracer

tracer = Tracer(
    log_level="detailed",
    show_reasoning=True,      # Shows the model's internal reasoning before tool selection
    show_raw_output=True,     # Shows exact model output before parsing
    show_schema_sent=True,    # Shows the exact schema that was sent to the model
    show_validation=True      # Shows validation pass/fail details
)

agent = Agent(
    model="your-model-here",
    tracer=tracer
)

This gives you a complete trace for every tool call:

[TRACE] Turn 3 - Tool Call Attempt
β”œβ”€β”€ User message: "Find orders for customer 42 that haven't shipped yet"
β”œβ”€β”€ Tools available: [get_customer_orders, search_knowledge_base]
β”œβ”€β”€ Schema sent to model: { ... full schema ... }
β”œβ”€β”€ Raw model output: "```json\n{\"tool\": \"get_customer_orders\", ...}\n```"
β”œβ”€β”€ Parsed output: {"customer_id": 42, "status_filter": "pending"}
β”œβ”€β”€ Validation: PASSED
β”œβ”€β”€ Execution result: {"orders": [...], "count": 3}
└── Fed back to model: Yes, 847 tokens

When something goes wrong, you can see exactly where it went wrong. Was the schema confusing? Did the model output something unparseable? Did validation reject something it shouldn't have? This is the difference between debugging for 10 minutes and debugging for 3 hours.


Getting Started Without the Pain

If you're new to OpenClaw and want to skip the week of banging your head against these failures, I'd genuinely recommend starting with Felix's OpenClaw Starter Pack. It's a pre-configured setup that includes sane defaults for all the stuff I just described β€” structured output handling, validation, retry policies, and tracing already wired up. You can rip it apart and customize it once you understand what each piece does, but starting from a working baseline instead of from scratch saves you a ton of frustration. Felix built it specifically because he kept seeing the same failure modes in the community over and over.


The Checklist

Here's what I run through every time tool calls start failing. Print this out, tape it to your monitor, whatever:

  1. Is structured output mode on? If no, turn it on. This is step zero.
  2. Are you validating before execution? Use SchemaValidator with strict_types=True.
  3. How many tools are active? If more than 8, use ToolGroup with dynamic_loading.
  4. Are your tool descriptions specific enough? Include explicit warnings about common mistakes.
  5. Do you have retry policies configured? At minimum, use SELF_CORRECT for validation errors.
  6. Is tracing on? If you can't see what's happening, you can't fix what's happening.
  7. Are you testing with the same model you'll deploy with? Tool calling performance varies wildly between models. Don't develop on GPT-4o and deploy on a 7B local model without re-testing everything.

The Uncomfortable Truth

Even with all of these fixes, tool calling with LLMs is not 100% reliable. It's not going to be 100% reliable for a while. Anyone telling you otherwise is selling something.

But the gap between "60% success rate with no error handling" and "95% success rate with proper validation, retries, and observability" is enormous. That's the difference between a demo and a product. OpenClaw gives you the infrastructure to close that gap without building it all from scratch.

The models will keep getting better at structured output. Constrained generation techniques will keep improving. But right now, today, the difference between agents that work and agents that don't isn't the model β€” it's the engineering around the model. It's the validation, the retry logic, the observability, and the schema design.

Do the boring stuff. Make your tool calls boringly reliable. That's how you actually ship.


Next Steps

  1. If you're just getting started, grab Felix's OpenClaw Starter Pack and work through the included examples. Each one demonstrates a different failure mode and its fix.
  2. If you're already building, go turn on Tracer right now and run your worst-performing workflow. Look at where it's actually breaking. I bet it's one of the five failure types above.
  3. If you're running local models, constrained generation is not optional. Enable JsonConstraint and accept the latency trade-off. The reliability improvement is worth it.
  4. Join the OpenClaw community channels and share your traces when things break. The more failure patterns people document, the better the tooling gets for everyone.

Stop guessing why your agents break. Start looking.

Claw Mart Daily

Get one AI agent tip every morning

Free daily tips to make your OpenClaw agent smarter. No spam, unsubscribe anytime.

More From the Blog