windmill-labs/windmill · error

Unknown tool call: ${functionName}. Probably not in the corr

Error message

Unknown tool call: ${functionName}. Probably not in the correct mode, use the change_mode tool to switch to the correct mode.

What it means

callTool looks up the requested function name among the tools registered for the CURRENT chat mode. If no tool matches, it throws this error hinting that the model is probably in the wrong mode and should call change_mode to switch. It signals a mode/toolset mismatch between what the model emitted and the active tool list.

Source

Thrown at frontend/src/lib/components/copilot/chat/shared.ts:717

	tools,
	functionName,
	args,
	workspace,
	helpers,
	toolCallbacks,
	toolId
}: {
	tools: Tool<T>[]
	functionName: string
	args: any
	workspace: string
	helpers: T
	toolCallbacks: ToolCallbacks
	toolId: string
}): Promise<string> {
	const tool = tools.find((t) => t.def.function.name === functionName)
	if (!tool) {
		throw new Error(
			`Unknown tool call: ${functionName}. Probably not in the correct mode, use the change_mode tool to switch to the correct mode.`
		)
	}
	const result = await tool.fn({ args, workspace, helpers, toolCallbacks, toolId })
	toolCompletionListener?.(functionName, args)
	return result
}

type MaybePromise<T> = T | Promise<T>

/** A refused tool call: the row the user reads, and the result the model gets. A bare string
 * is both at once. */
export type ToolRejection = string | { label: string; result: string }

function normalizeToolRejection(
	rejection: ToolRejection | undefined
): { label: string; result: string } | undefined {
	if (rejection === undefined) return undefined

View on GitHub (pinned to e474e8803c)

Solutions

  1. Follow the hint: have the model call the change_mode tool to switch to the mode that owns the tool, then retry the call.
  2. Verify the frontend/chat mode matches the user's intent (script vs flow vs app) before continuing the conversation.
  3. If the tool name looks right but fails, check for recent renames of the tool def and re-send with the current name.
  4. Restart the chat so the toolset registration and model state are consistent.

Example fix

// before (model in script mode calls flow tool)
{"name": "update_flow_value", ...} // -> Unknown tool call
// after
{"name": "change_mode", "arguments": {"new_mode": "flow"}}
then
{"name": "update_flow_value", ...}
Defensive patterns

Strategy: try-catch

Validate before calling

const available = tools.map((t) => t.def.function.name)
if (!available.includes(functionName)) {
  // ask the model to call change_mode first, or remap the name
  return requestModeSwitch(knownAliasFor(functionName))
}

Type guard

function isKnownTool<T extends { def: { function: { name: string } } }>(tools: T[], name: string): T | undefined {
  return tools.find((t) => t.def.function.name === name)
}

Try / catch

try {
  result = await callTool({ functionName, tools, ... })
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Unknown tool call:')) {
    // feed the message back to the model instructing a change_mode call, then retry
  }
}

Prevention

When it happens

Trigger: The model emitted a tool call whose function name exists only in another mode's toolset (e.g. an app-mode or flow-mode tool while the chat is in script mode), or a stale/renamed tool name was sent, and processToolCall passed it to callTool where tools.find returned undefined.

Common situations: User switches topics mid-chat (asks a flow question while in script mode) and the model guesses a flow tool name; the model hallucinates a tool name not in any toolset; frontend mode state drifted from the toolset that was registered in the LLM request.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/e25b07a8c6f58aee. Report an issue: GitHub.