n8n-io/n8n · error · Error
Tool name "${tool.name}" is reserved for SDK built-in tools
Error message
Tool name "${tool.name}" is reserved for SDK built-in tools What it means
Thrown by Agent.assertReservedSdkBuiltInToolName when a registered tool's name is in SDK_RESERVED_BUILTIN_TOOL_NAMES (currently 'delegate_subagent' and 'write_todos') but the tool is not the genuine SDK-built instance. Genuine status is detected by isDelegateSubAgentTool (for delegate_subagent) or isSdkOwnedBuiltInTool (metadata key 'sdkOwnedBuiltinTool' === true).
Source
Thrown at packages/@n8n/agents/src/sdk/agent.ts:1388
this.assertToolNameAvailable(tool.name);
this.assertReservedSdkBuiltInToolName(tool);
}
private assertToolNameAvailable(toolName: string): void {
if (!this.hasRuntimeSkillTool || !RUNTIME_SKILL_TOOL_NAMES.has(toolName)) return;
throw new Error(`Tool name "${toolName}" is reserved for runtime skills`);
}
private assertReservedSdkBuiltInToolName(tool: BuiltTool): void {
if (!SDK_RESERVED_BUILTIN_TOOL_NAMES.has(tool.name)) return;
if (isDelegateSubAgentTool(tool)) {
if (tool.name === DELEGATE_SUB_AGENT_TOOL_NAME) return;
} else if (isSdkOwnedBuiltInTool(tool)) {
return;
}
throw new Error(`Tool name "${tool.name}" is reserved for SDK built-in tools`);
}
private removeRuntimeSkillTools(): void {
if (!this.hasRuntimeSkillTool) return;
this.tools = this.tools.filter((tool) => !RUNTIME_SKILL_TOOL_NAMES.has(tool.name));
this.hasRuntimeSkillTool = false;
}
}
function resolveInlineSubAgentModelConfig(
request: DelegateSubAgentRequest,
options: {
modelConfig: ModelConfig;
inlineSubAgentModelsByDifficulty?: Partial<Record<SubAgentTaskDifficulty, ModelConfig>>;
},
): ModelConfig {
if (request.difficulty === undefined) {View on GitHub (pinned to 5ac6606e81)
Solutions
- Use createDelegateSubAgentTool / the official write-todos factory so the tool carries correct branding and metadata.
- Rename your custom tool away from 'delegate_subagent' and 'write_todos'.
- If you must wrap an SDK built-in, brand it with withSdkOwnedBuiltInMetadata or pass the already-branded instance.
Example fix
// before
agent.tool({ name: 'write_todos', execute: async () => ... });
// after
agent.tool({ name: 'my-todos', execute: async () => ... }); Defensive patterns
Strategy: validation
Validate before calling
import { SDK_RESERVED_BUILTIN_TOOL_NAMES } from '@n8n/agents';
function assertNotReservedBuiltin(name: string) {
if (SDK_RESERVED_BUILTIN_TOOL_NAMES.has(name)) {
throw new Error(`Tool name ${name} collides with SDK built-in; rename or use the factory.`);
}
} Type guard
function isReservedBuiltinName(name: string): boolean {
return name === 'delegate_subagent' || name === 'write_todos';
} Prevention
- Prefer SDK factories (createDelegateSubAgentTool, createWriteTodosTool) over hand-built tools.
- Maintain a deny-list of reserved names and lint tool registrations against it.
When it happens
Trigger: Registering a hand-rolled tool whose name is 'delegate_subagent' or 'write_todos' without the SDK ownership metadata or delegate-tool branding. Triggered from .tool() / deferred tool registration through assertToolRegistrationAllowed.
Common situations: User copies the SDK built-in tool name by accident; a forked/modified tool loses the metadata stamp; constructing a delegate sub-agent tool with a custom factory that bypasses createDelegateSubAgentTool.
Related errors
- Deferred tool name "${tool.name}" is reserved
- 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
- Duplicate deferred tool name "${tool.name}"
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/a5a5e0293cd2e5b8.
Report an issue: GitHub.