vercel/ai · error
Tool "${toolCall.toolName}" does not have an execute functio
Error message
Tool "${toolCall.toolName}" does not have an execute function. Client-side tools should be filtered before calling executeTool. What it means
executeTool requires the resolved tool to have an `execute` function. Tools without execute (client-side / confirm-style tools) must be filtered out before executeTool is called; encountering one here means an unexecutable tool reached server-side execution.
Source
Thrown at packages/workflow/src/workflow-agent.ts:2951
messages: LanguageModelV4Prompt,
): ModelMessage[] {
const withoutAssistantToolCall =
messages.at(-1)?.role === 'assistant' ? messages.slice(0, -1) : messages;
return withoutAssistantToolCall as unknown as ModelMessage[];
}
async function executeTool(
toolCall: { toolCallId: string; toolName: string; input: unknown },
tools: ToolSet,
messages: LanguageModelV4Prompt,
context?: unknown,
download?: DownloadFunction,
sandbox?: SandboxSession,
): Promise<WorkflowToolExecutionResult> {
const tool = tools[toolCall.toolName];
if (!tool) throw new Error(`Tool "${toolCall.toolName}" not found`);
if (typeof tool.execute !== 'function') {
throw new Error(
`Tool "${toolCall.toolName}" does not have an execute function. ` +
`Client-side tools should be filtered before calling executeTool.`,
);
}
// Input is already parsed and validated by streamModelCall's parseToolCall
const parsedInput = toolCall.input;
let toolResult: unknown;
try {
// Extract execute function to avoid binding `this` to the tool object.
// If we called `tool.execute(...)` directly, JavaScript would bind `this`
// to `tool`, which contains non-serializable properties like `inputSchema`.
// When the execute function is a workflow step (marked with 'use step'),
// the step system captures `this` for serialization, causing failures.
const { execute } = tool;
toolResult = await execute(parsedInput, {
toolCallId: toolCall.toolCallId,
// Pass the conversation messages to the tool so it has context about the conversationView on GitHub (pinned to 69428b1f8b)
Solutions
- Filter out tools without an execute function before calling executeTool.
- Add an execute implementation to the tool if it should run server-side.
- Handle client-side tools in the client layer so the model's call never reaches executeTool.
Example fix
// before
await executeTool({ toolCall, tools });
// after
const serverTools = Object.fromEntries(
Object.entries(tools).filter(([, t]) => typeof t.execute === 'function'),
);
await executeTool({ toolCall, tools: serverTools }); Defensive patterns
Strategy: validation
Validate before calling
const executable = Object.values(tools).every(t => typeof t.execute === 'function');
if (!executable) {
// remove or handle tools lacking execute before calling executeTool
} Type guard
function hasExecute(t: unknown): t is { execute: Function } {
return !!t && typeof (t as any).execute === 'function';
} Try / catch
try {
await executeTool({ toolCall, tools });
} catch (e) {
if (e instanceof Error && e.message.includes('does not have an execute function')) {
// route to client-side handling / confirmation flow
} else throw e;
} Prevention
- Maintain separate registries for server-side (execute) and client-side tools.
- Filter with hasExecute before invoking executeTool.
- Implement confirmation-style tools on the client layer explicitly.
When it happens
Trigger: A tool defined without `execute` (e.g. a tool needing human confirmation or client-side execution) is included in the tools map handed to executeTool, and the model calls it.
Common situations: Human-in-the-loop confirmation tools not filtered before the server step; mixing client tools into a server tools registry; constructing tools programmatically and forgetting execute.
Related errors
- Tool "${toolCall.toolName}" not found
- [WorkflowAgent] Provider-executed tool "${toolCall.toolName}
- The ${model.provider} model "${model.modelId}" does not supp
- unknown tool "${toolName}".
- callers for tool "${toolName}" must be an array.
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/b989b6a901795182.
Report an issue: GitHub.