n8n-io/n8n · error · Error
Duplicate deferred tool name "${tool.name}"
Error message
Duplicate deferred tool name "${tool.name}" What it means
`DeferredToolManager` indexes tools by name in a `Map` for O(1) lookup by `load_tool`. If two tools in the constructor array share the same `name`, the second would overwrite the first, silently hiding a tool. The constructor detects this and throws to fail fast rather than silently lose a tool.
Source
Thrown at packages/@n8n/agents/src/runtime/tools/deferred-tool-manager.ts:96
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;
}
get loadedToolCount(): number {View on GitHub (pinned to 5ac6606e81)
Solutions
- Identify the duplicate name from the error message.
- Rename one of the conflicting tools, or deduplicate by keeping only the intended one.
- Implement a namespacing/prefixing strategy when merging tools from multiple sources (e.g. prefix with the MCP server name).
- Add a pre-construction deduplication step: build a `Map<string, BuiltTool>` and handle collisions before passing to the manager.
Example fix
// before:
const mgr = new DeferredToolManager([
{ name: 'read_file', ... }, // from MCP server A
{ name: 'read_file', ... }, // from MCP server B
]);
// after: namespace by source
const mgr = new DeferredToolManager([
{ name: 'serverA__read_file', ... },
{ name: 'serverB__read_file', ... },
]); Defensive patterns
Strategy: validation
Validate before calling
function deduplicateTools(tools: BuiltTool[]): BuiltTool[] {
const seen = new Map<string, BuiltTool>();
for (const tool of tools) {
if (seen.has(tool.name)) {
logger.warn(`Duplicate tool name "${tool.name}", keeping first`);
continue;
}
seen.set(tool.name, tool);
}
return [...seen.values()];
}
const mgr = new DeferredToolManager(deduplicateTools(allTools)); Type guard
function hasUniqueNames(tools: BuiltTool[]): boolean {
const names = tools.map((t) => t.name);
return new Set(names).size === names.length;
} Prevention
- Deduplicate tools by name before constructing DeferredToolManager.
- Namespace tools from different sources (e.g. `serverA__read_file`).
- Validate tool name uniqueness at assembly time.
When it happens
Trigger: Passing `new DeferredToolManager([toolA, toolB])` where `toolA.name === toolB.name`. Common when tools are aggregated from multiple sources (MCP servers, built-in catalog, custom tools) and two sources define a tool with the same name.
Common situations: Two MCP servers expose a tool with the same name (e.g. both have `read_file`). A built-in tool and a custom tool share a name. A tool list was accidentally duplicated during assembly.
Related errors
- Deferred tool name "${tool.name}" is reserved
- ${toolName} policy.maxChildren must be a finite positive int
- ${toolName} policy.maxChildren must be at least 1
- Invalid delegate sub-agent tool name "${name}": must start w
- Sub-agent task name must contain at least one alphanumeric c
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/349c6b89eeb066e6.
Report an issue: GitHub.