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:
- Execute the Bash tool successfully
- Display the date
- Continue without errors
Actual Behavior
The model:
- Executes the Bash tool successfully (gets the date)
- Receives an error message: "Model tried to call unavailable tool 'Bash'. No tools are available."
- 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
- Provider emits tool call with
providerExecuted: true and dynamic: true
- AI SDK passes it to
parseToolCall() without checking these flags
parseToolCall() tries to validate against user's tools map
- Validation fails because provider-executed tools aren't in user's tools map
- Tool marked as
invalid: true
- Later code (lines 2367-2379) filters for
invalid && dynamic tools and creates error outputs
- 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:
Bug Report: parseToolCall validates provider-executed tools causing invalid tool errors
Description
The AI SDK's
parseToolCallfunction incorrectly validates provider-executed tool calls (tools withproviderExecuted: true), causing them to be marked asinvalid: trueeven 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
Minimal Reproduction
Expected Behavior
The model should:
Actual Behavior
The model:
Output:
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
parseToolCallfunction (src/generate-text/parse-tool-call.ts, built todist/index.mjs:1841-1896).Current Code Flow
The Problem
providerExecuted: trueanddynamic: trueparseToolCall()without checking these flagsparseToolCall()tries to validate against user'stoolsmapinvalid: trueinvalid && dynamictools and creates error outputsProposed Fix
Skip validation for provider-executed tools:
Impact
providerExecuted: truefor built-in toolsstepCountIs(), etc.) that trigger tool validationRelated Issues
Additional Context
The provider correctly sets both
providerExecuted: trueanddynamic: trueas 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: