windmill-labs/windmill · error
Invalid AI agent tool name(s): ${invalidToolNames.map((t) =>
Error message
Invalid AI agent tool name(s): ${invalidToolNames.map((t) => `agent "${t.agentId}" tool "${t.toolId}" is named ${JSON.stringify(t.name)} - ${t.error}`).join('; ')}. The tool's "summary" is the name the agent calls it by: use underscores instead of spaces (e.g. "search_docs"). What it means
validateFlowModules rejects AiAgent tools whose `summary` (the name the LLM calls the tool by) contains characters outside ^[a-zA-Z0-9_]+$. The worker enforces this regex at run time, so a flow with a spaced tool name saves fine but fails on every run — this check moves the failure to edit time. The message lists each offending agent id, tool id, current name, and the specific reason.
Source
Thrown at frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts:285
// Not expressible in the schema: `provider` is required only when the step is standalone, and
// making AiAgent a conditional union breaks the FlowModuleValue discriminated union it belongs to.
const providerless = collectProviderlessAgentIds(parsedModules)
if (providerless.length > 0) {
throw new Error(
`AI agent modules ${providerless
.map((id) => `"${id}"`)
.join(
', '
)} need a provider input transform, or an "agent" path linking them to a saved agent`
)
}
// An agent tool's `summary` is the name the LLM sees; the worker rejects anything outside
// `^[a-zA-Z0-9_]+$`, so a flow written with a spaced name saves but fails on every run.
const invalidToolNames = collectInvalidAgentToolNames(parsedModules)
if (invalidToolNames.length > 0) {
throw new Error(
`Invalid AI agent tool name(s): ${invalidToolNames
.map(
(t) =>
`agent "${t.agentId}" tool "${t.toolId}" is named ${JSON.stringify(t.name)} - ${t.error}`
)
.join(
'; '
)}. The tool's "summary" is the name the agent calls it by: use underscores instead of spaces (e.g. "search_docs").`
)
}
validateAiAgentProviders(parsedModules, ctx.aiProviders, ctx.aiProviderWarnings)
return parsedModules
}
export function validateFlowSchema(rawSchema: unknown): Record<string, any> | null {
if (rawSchema == null) return nullView on GitHub (pinned to e474e8803c)
Solutions
- Rename each listed tool's `summary` to a snake_case identifier matching ^[a-zA-Z0-9_]+$ (e.g. "search_docs").
- Re-run the modules tool after fixing all listed agent/tool pairs.
- Keep tool summaries as identifier-style names and put human descriptions in the tool's `description` field instead.
Example fix
// before
{"type":"aiagent","tools":[{"id":"t1","summary":"Search Docs"}]}
// after
{"type":"aiagent","tools":[{"id":"t1","summary":"search_docs"}]} Defensive patterns
Strategy: validation
Validate before calling
const TOOL_NAME_RE = /^[a-zA-Z0-9_]+$/
const bad = []
for (const m of flow.modules ?? []) {
if (m?.value?.type !== 'aiagent') continue
for (const t of m.value.tools ?? []) {
if (typeof t?.summary === 'string' && !TOOL_NAME_RE.test(t.summary))
bad.push({agentId: m.id, toolId: t.id, name: t.summary})
}
}
if (bad.length) throw new Error(`Invalid tool summaries: ${JSON.stringify(bad)}`) Type guard
function isValidToolName(name) {
return typeof name === 'string' && /^[a-zA-Z0-9_]+$/.test(name)
} Try / catch
try {
const editable = validateEditableFlowJson(rawFlow)
} catch (e) {
if (String(e.message).startsWith('Invalid AI agent tool name')) {
// ask the model to rename each listed tool summary to snake_case
} else throw e
} Prevention
- Generate tool summaries as snake_case identifiers only
- Put human-readable text in tool descriptions, never in summaries
- Run the tool-name regex over agent JSON before submitting a patch
When it happens
Trigger: Calling flowTools/modules with a flow JSON where an aiagent module's tool has a `summary` like "Search Docs" or contains hyphens, spaces, or unicode instead of snake_case identifiers.
Common situations: An LLM copies a human-readable tool title into `summary`; a user writes tool names with spaces; localized or formatted names are pasted into tool summaries.
Related errors
- Invalid migration name '${name}': use only letters, digits,
- Fork workspace name is too long (${effectiveName.length} cha
- Fork workspace id \`${id}\` is invalid: ${reason}. Choose a
- Invalid datatable migration path: ${path}
- Invalid AI agent provider configuration: ${errors.join('\n')
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/17b88bbdc1367066.
Report an issue: GitHub.