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 conversation

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Filter out tools without an execute function before calling executeTool.
  2. Add an execute implementation to the tool if it should run server-side.
  3. 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

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


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/b989b6a901795182. Report an issue: GitHub.