◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/Claude.ai
◉ Claude.ai/2026-05-04Intermediate

How to Fix the "Tool Result Could Not Be Submitted" Error in Claude

A practical guide to diagnosing and fixing the "tool result could not be submitted" error in Claude's tool use API, based on real development experience.

tool-use24error-fix4API28troubleshooting92tool-result

If you're building with Claude's tool use API, you've probably run into the "tool result could not be submitted" error at some point. It's one of those frustrating errors where the message alone doesn't tell you much about what actually went wrong.

I've hit this error more times than I'd like to admit while implementing tool use in my own projects. After working through the patterns, here's a practical breakdown of the causes and fixes.

When Does This Error Appear?

"Tool result could not be submitted" appears during the tool use flow, which works like this:

  1. A user sends a message
  2. Claude decides to call a tool and returns a response containing a tool_use block
  3. Your code executes the tool and retrieves a result
  4. You send that result back as a tool_result in the next message

When step four fails, you get this error. The message might suggest the problem is on Claude's end, but in most cases it's an implementation issue you can fix.

Common Causes and Fixes

1. Tool Result Is Too Large (Most Common)

This is the most frequent culprit. If you pass raw HTML from a web scraper, a large database dump, or any unfiltered external data as your tool result, you can easily exceed Claude's context window.

Most Claude models support up to roughly 200,000 tokens of context. When the total conversation — including tool results — approaches that limit, the API can't accept new submissions.

The fix: Pre-process your tool results and return only the information Claude actually needs.

# ❌ Returning raw HTML (dangerous)
def search_web(query: str) -> str:
    response = requests.get(f"https://example.com/search?q={query}")
    return response.text  # Could be tens of thousands of characters
 
# ✅ Extracting only what matters (correct)
def search_web(query: str) -> str:
    response = requests.get(f"https://example.com/search?q={query}")
    soup = BeautifulSoup(response.text, 'html.parser')
    
    results = []
    for item in soup.select('.result')[:5]:  # Top 5 results only
        title = item.select_one('h3')
        snippet = item.select_one('.snippet')
        if title and snippet:
            results.append(f"Title: {title.text}\nSummary: {snippet.text[:200]}")
    
    return "\n\n".join(results) if results else "No results found"

A good rule of thumb: keep each individual tool result under 5,000–10,000 tokens.

2. Mismatched tool_use_id

Claude's tool_use block includes an id field. Your tool_result must reference that exact ID in tool_use_id. Any mismatch will cause the submission to fail.

response = anthropic.messages.create(...)
 
# ❌ Hardcoding or guessing the ID
tool_results = [{
    "type": "tool_result",
    "tool_use_id": "toolu_01",  # This will fail unless it matches exactly
    "content": result_content
}]
 
# ✅ Using the ID from Claude's actual response
for block in response.content:
    if block.type == "tool_use":
        tool_result = {
            "type": "tool_result",
            "tool_use_id": block.id,  # ← Use the ID Claude gave you
            "content": str(tool_output)
        }

This becomes especially tricky when Claude calls multiple tools in parallel — make sure you're pairing each result with the correct ID.

3. Wrong content Type

The content field in a tool_result must be a string or an array (for multi-modal content including images). Passing None, integers, or other types will cause issues.

# ❌ Passing non-string types
"content": None
"content": 42
 
# ✅ Always convert to string
"content": str(result) if result is not None else "No result returned from tool"

When a tool execution fails, use is_error: true instead of an empty string:

{
    "type": "tool_result",
    "tool_use_id": block.id,
    "content": "Error: Database connection timed out after 30 seconds",
    "is_error": True  # Claude will recognize this and try to recover
}

4. Network or Timeout Issues

Long-running tools can cause the underlying HTTP connection to time out before you can submit the result. This is common with tools that call external APIs or process large files.

import anthropic
import httpx
 
# Set a longer timeout explicitly
client = anthropic.Anthropic(
    http_client=httpx.Client(timeout=60.0)  # 60 seconds
)

For tools that genuinely take a long time, consider breaking the work into smaller chunks or using asynchronous processing.

5. Malformed Message History

As conversations grow, it's easy for the messages array structure to become inconsistent. Tool use has strict requirements about message ordering and format.

# Correct message structure for tool use
messages = [
    # 1. User message
    {"role": "user", "content": "What's the weather in Tokyo?"},
    
    # 2. Claude's tool call response
    {"role": "assistant", "content": [
        {"type": "text", "text": "Let me check the weather for you."},
        {"type": "tool_use", "id": "toolu_01", "name": "get_weather", "input": {"city": "Tokyo"}}
    ]},
    
    # 3. Tool result (sent as "user" role)
    {"role": "user", "content": [
        {"type": "tool_result", "tool_use_id": "toolu_01", "content": "Tokyo: Sunny, 25°C"}
    ]},
    
    # 4. Continue the conversation...
]

Every assistant message that includes a tool_use block must be followed by a user message with the corresponding tool_result before you can continue the conversation.

Debugging Checklist

When you hit this error, work through these checks in order:

Verify the tool_use_id match first — print both the ID from Claude's response and the ID in your tool_result and confirm they're identical. Then check the result size by printing len(result_content) to see if you're returning unexpectedly large data. After that, check the content type with type(result_content) and ensure it's a str. Finally, inspect the message array structure to confirm roles alternate correctly and every tool call has a corresponding result.

A Note on Streaming

If you're using the streaming API, the same root causes apply, but tracking them is harder because the response comes in chunks. I'd recommend debugging in non-streaming mode first to confirm the tool use logic is correct, then switching to streaming once everything works.

Wrapping Up

The "tool result could not be submitted" error almost always comes down to one of these five patterns. Oversized results are the most surprising one — code that works fine in development can suddenly fail in production when it encounters real-world data volumes.

Adding explicit size limits to your tool implementations early in development will save you significant debugging time later.

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

◉ Claude.ai2026-04-09
Complete Fix Guide: Claude File Attachment and Image Recognition Failures
Solve issues where PDFs, images, or text files attached to Claude aren't loading or throw errors. This guide covers file format support, size limits, and browser settings—everything you need to fix file upload problems.
◉ Claude.ai2026-03-30
When Claude Won't Respond, Runs Slow, or Stops Mid-Answer: How I Narrow It Down
A step-by-step way to isolate why Claude isn't responding, is unusually slow, or stops mid-answer: service, browser, then account. Includes a tiny script to measure your own busy hours, fixes for Claude Code Auto Mode and MCP stalls, and how to use the SDK's timeout and automatic retries so unattended jobs never freeze in silence.
◉ Claude.ai2026-07-09
Claude Card Declined: Narrowing Down Who Actually Blocked the Payment
When your card is declined on Claude Pro or Max, the block comes from your issuing bank, from Stripe, or from Anthropic. Here is how the decline codes a merchant actually sees map to the three checks you can run yourself — international blocks, 3-D Secure, and postal code mismatches.