Skip to content

parseToolCall validates provider-executed tools causing invalid tool errors #10888

Description

@pablof7z

Bug Report: parseToolCall validates provider-executed tools causing invalid tool errors

Description

The AI SDK's parseToolCall function incorrectly validates provider-executed tool calls (tools with providerExecuted: true), causing them to be marked as invalid: true even though they execute successfully. This results in error messages being sent back to the model, confusing it into apologizing for tools it just successfully used.

Reproduction

Environment

  • AI SDK version: 5.0.106
  • Provider: ai-sdk-provider-claude-code v2.2.3
  • Runtime: Bun

Minimal Reproduction

import { stepCountIs, streamText } from 'ai';
import { createClaudeCode } from 'ai-sdk-provider-claude-code';

const bashOnlyClaude = createClaudeCode({
  defaultSettings: {
    allowedTools: ["Bash(echo:*)", "Bash(date)", "Bash(pwd)"],
  },
});

const result = streamText({
  stopWhen: stepCountIs(10),
  model: bashOnlyClaude("haiku"),
  prompt: "Can you show me the current date? Use the date command.",
});

let response = "";
for await (const chunk of result.textStream) {
  response += chunk;
}
console.log(response);

Expected Behavior

The model should:

  1. Execute the Bash tool successfully
  2. Display the date
  3. Continue without errors

Actual Behavior

The model:

  1. Executes the Bash tool successfully (gets the date)
  2. Receives an error message: "Model tried to call unavailable tool 'Bash'. No tools are available."
  3. Apologizes for not being able to use the tool it just successfully used

Output:

I'll run the date command for you.
The current date is Thursday, December 4, 2025 at 12:47:33 PST.

I apologize for the confusion. It appears that the Bash tool is not currently available in this context...

Debug Output

Tool call events show the problem:

{
  "type": "tool-call",
  "toolCallId": "toolu_01J6t3ADEiYLe1Bct6TdmoLq",
  "toolName": "Bash",
  "dynamic": true,
  "invalid": true,  // ← Incorrectly marked as invalid
  "error": {
    "name": "AI_NoSuchToolError",
    "toolName": "Bash"
  }
}
{
  "type": "tool-result",
  "toolCallId": "toolu_01J6t3ADEiYLe1Bct6TdmoLq",
  "toolName": "Bash",
  "output": "Thu Dec  4 12:47:33 PST 2025",  // ← Tool executed successfully!
  "providerExecuted": true
}

Error message sent to model:

{
  "role": "tool",
  "content": [{
    "type": "tool-result",
    "toolCallId": "toolu_01J6t3ADEiYLe1Bct6TdmoLq",
    "toolName": "Bash",
    "output": {
      "type": "error-text",
      "value": "Model tried to call unavailable tool 'Bash'. No tools are available."
    }
  }]
}

Root Cause Analysis

The issue is in parseToolCall function (src/generate-text/parse-tool-call.ts, built to dist/index.mjs:1841-1896).

Current Code Flow

async function parseToolCall({ toolCall, tools, ... }) {
  try {
    if (tools == null) {
      throw new NoSuchToolError({ toolName: toolCall.toolName });
    }
    // Tries to validate ALL tool calls, including provider-executed ones
    return await doParseToolCall({ toolCall, tools });
  } catch (error) {
    // When validation fails, marks tool as invalid
    return {
      type: "tool-call",
      toolCallId: toolCall.toolCallId,
      toolName: toolCall.toolName,
      input,
      dynamic: true,
      invalid: true,  // ← Problem: marked invalid even though providerExecuted
      error,
      ...
    };
  }
}

The Problem

  1. Provider emits tool call with providerExecuted: true and dynamic: true
  2. AI SDK passes it to parseToolCall() without checking these flags
  3. parseToolCall() tries to validate against user's tools map
  4. Validation fails because provider-executed tools aren't in user's tools map
  5. Tool marked as invalid: true
  6. Later code (lines 2367-2379) filters for invalid && dynamic tools and creates error outputs
  7. Error sent back to model, causing confusion

Proposed Fix

Skip validation for provider-executed tools:

async function parseToolCall({ toolCall, tools, ... }) {
  // Skip validation for provider-executed tools
  if (toolCall.providerExecuted === true) {
    const parsedInput = await safeParseJSON({ text: toolCall.input });
    return {
      type: "tool-call",
      toolCallId: toolCall.toolCallId,
      toolName: toolCall.toolName,
      input: parsedInput.success ? parsedInput.value : toolCall.input,
      dynamic: toolCall.dynamic,
      providerExecuted: true,
      providerMetadata: toolCall.providerMetadata
    };
  }

  // Existing validation logic for user-defined tools
  try {
    if (tools == null) {
      throw new NoSuchToolError({ toolName: toolCall.toolName });
    }
    return await doParseToolCall({ toolCall, tools });
  } catch (error) {
    ...
  }
}

Impact

  • Affects all providers that use providerExecuted: true for built-in tools
  • Only manifests when using step-based features (stepCountIs(), etc.) that trigger tool validation
  • Does not affect simple streaming without step management
  • Causes confusing UX where models apologize for successfully used tools

Related Issues

Additional Context

The provider correctly sets both providerExecuted: true and dynamic: true as specified in the AI SDK v3 protocol. The issue is purely in the SDK's validation logic not respecting these flags before attempting validation.

Sources:

Metadata

Metadata

Assignees

No one assigned

    Labels

    ai/corecore functions like generateText, streamText, etc. Provider utils, and provider spec.ai/providerrelated to a provider package. Must be assigned together with at least one `provider/*` labelprovider/communitytask-identify-issue-type-done

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions