n8n-io/n8n · error · Error
Deferred tool name "${tool.name}" is reserved
Error message
Deferred tool name "${tool.name}" is reserved What it means
`DeferredToolManager` exposes two built-in controller tools — `search_tools` and `load_tool` — that let the agent discover and load tools on demand. To avoid name collisions, any tool passed to the constructor whose `name` matches these reserved names throws immediately. The manager cannot function if a deferred tool shadows its own controllers.
Source
Thrown at packages/@n8n/agents/src/runtime/tools/deferred-tool-manager.ts:93
export interface DeferredToolManagerOptions {
topK?: number;
}
export class DeferredToolManager {
private readonly toolsByName = new Map<string, BuiltTool>();
private readonly loadedToolNames = new Set<string>();
private readonly topK: number;
private readonly searchTool: BuiltTool;
private readonly loadTool: BuiltTool;
constructor(tools: BuiltTool[], options: DeferredToolManagerOptions = {}) {
for (const tool of tools) {
if (tool.name === SEARCH_TOOLS_TOOL_NAME || tool.name === LOAD_TOOL_TOOL_NAME) {
throw new Error(`Deferred tool name "${tool.name}" is reserved`);
}
if (this.toolsByName.has(tool.name)) {
throw new Error(`Duplicate deferred tool name "${tool.name}"`);
}
this.toolsByName.set(tool.name, tool);
}
this.topK = options.topK ?? DEFAULT_TOP_K;
this.searchTool = this.createSearchTool();
this.loadTool = this.createLoadTool();
}
get hasTools(): boolean {
return this.toolsByName.size > 0;
}
get totalToolCount(): number {
return this.toolsByName.size;View on GitHub (pinned to 5ac6606e81)
Solutions
- Rename the conflicting tool to something other than `search_tools` or `load_tool`.
- If the name comes from an external source (MCP server, tool catalog), map/rename it before passing to the constructor.
- Filter or validate tool names against `[SEARCH_TOOLS_TOOL_NAME, LOAD_TOOL_TOOL_NAME]` before constructing the manager.
Example fix
// before:
const mgr = new DeferredToolManager([
{ name: 'search_tools', ... }, // collides with built-in
]);
// after:
const mgr = new DeferredToolManager([
{ name: 'find_integrations', ... }, // renamed
]); Defensive patterns
Strategy: validation
Validate before calling
import { SEARCH_TOOLS_TOOL_NAME, LOAD_TOOL_TOOL_NAME } from './deferred-tool-manager';
const RESERVED = new Set([SEARCH_TOOLS_TOOL_NAME, LOAD_TOOL_TOOL_NAME]);
function sanitizeToolNames(tools: BuiltTool[]): BuiltTool[] {
return tools.filter((t) => {
if (RESERVED.has(t.name)) {
logger.warn(`Tool name "${t.name}" is reserved, skipping`);
return false;
}
return true;
});
}
const mgr = new DeferredToolManager(sanitizeToolNames(allTools)); Type guard
function isNotReservedToolName(tool: BuiltTool): boolean {
return tool.name !== 'search_tools' && tool.name !== 'load_tool';
} Prevention
- Filter tool names against the reserved set before constructing DeferredToolManager.
- Namespace MCP-imported tool names to avoid collisions with built-in names.
- Validate tool names at registration time.
When it happens
Trigger: Passing a tool with `name: 'search_tools'` or `name: 'load_tool'` to `new DeferredToolManager([...])`. This happens when a user-defined or integration tool happens to use one of these names.
Common situations: A custom tool or MCP-imported tool was named `search_tools` or `load_tool`. A tool catalog contains a tool with a colliding name. A code generation or naming convention accidentally produced a reserved name.
Related errors
- Duplicate deferred tool name "${tool.name}"
- Tool name "${reservedTool.name}" is reserved for runtime ski
- Deferred tool name collision — the following tool names reso
- Tool name "${toolName}" is reserved for runtime skills
- Tool name "${tool.name}" is reserved for SDK built-in tools
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/30b730214841ccb4.
Report an issue: GitHub.