n8n-io/n8n · error · Error
Tool name "${reservedTool.name}" is reserved for runtime ski
Error message
Tool name "${reservedTool.name}" is reserved for runtime skills What it means
Thrown by Agent.skills() when an existing tool in the agent's tool list has a name that is in the RUNTIME_SKILL_TOOL_NAMES reserved set. The library reserves specific tool names for its own runtime skill management tools (search_tools, load_tool), and registering a user tool with one of these names would shadow the runtime's ability to discover and load skills.
Source
Thrown at packages/@n8n/agents/src/sdk/agent.ts:333
}
/**
* Add runtime-loadable skills to the agent. The model sees only a compact
* name/description catalog in the system prompt, then calls `load_skill`
* to retrieve the full instructions for a relevant skill.
*/
skills(sourceOrSkills: RuntimeSkillSource | RuntimeSkill[]): this {
const source = Array.isArray(sourceOrSkills)
? createRuntimeSkillSource(sourceOrSkills)
: sourceOrSkills;
this.removeRuntimeSkillTools();
this.skillSource = source;
if (source.registry.skills.length === 0) return this;
const reservedTool = this.tools.find((tool) => RUNTIME_SKILL_TOOL_NAMES.has(tool.name));
if (reservedTool) {
throw new Error(`Tool name "${reservedTool.name}" is reserved for runtime skills`);
}
this.tools.push(...createRuntimeSkillTools(source));
this.hasRuntimeSkillTool = true;
return this;
}
/** Add a provider-defined tool (e.g. Anthropic web search, OpenAI code interpreter). */
providerTool(builtProviderTool: BuiltProviderTool): this {
this.providerTools.push(builtProviderTool);
return this;
}
/** Read the declared tools. Lists only tools added via tool() */
get declaredTools(): BuiltTool[] {
return this.tools;
}
View on GitHub (pinned to 5ac6606e81)
Solutions
- Rename the conflicting tool to a non-reserved name before calling .skills().
- Check the RUNTIME_SKILL_TOOL_NAMES set (exported from the package) before naming custom tools.
- Add skills before adding custom tools, so you know which names are taken.
Example fix
// before agent.tools([myTool]).skills(skillSource); // myTool.name === 'search_tools' // after myTool.name = 'my_search'; agent.tools([myTool]).skills(skillSource);
Defensive patterns
Strategy: validation
Validate before calling
import { RUNTIME_SKILL_TOOL_NAMES } from '@n8n/agents';
function hasReservedToolName(tools: { name: string }[]): boolean {
return tools.some((t) => RUNTIME_SKILL_TOOL_NAMES.has(t.name));
}
if (hasReservedToolName(myTools)) {
// rename before calling .skills()
} Type guard
function isReservedSkillToolName(name: string, reserved: Set<string>): name is string {
return reserved.has(name);
} Prevention
- Check tool names against RUNTIME_SKILL_TOOL_NAMES before adding skills.
- Avoid naming custom tools 'search_tools' or 'load_tool'.
- Add skills before custom tools so reserved names are known upfront.
When it happens
Trigger: Calling agent.skills(source) after having added a tool via .tools() whose name matches one of the reserved runtime skill tool names. Or calling .tools() with a reserved name and then activating skills.
Common situations: Naming a custom tool 'search_tools' or 'load_tool' (or whatever names are in RUNTIME_SKILL_TOOL_NAMES). Importing tools from another library that happens to use a reserved name. Copying tool names from documentation without checking against the reserved set.
Related errors
- Tool name "${toolName}" is reserved for runtime skills
- Deferred tool name "${tool.name}" is reserved
- Static tool name collision — the following tool names resolv
- Deferred tool name collision — the following tool names reso
- MCP tool name collision — the following tool names resolve t
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/0d575aa9b8652c7b.
Report an issue: GitHub.