n8n-io/n8n · error · InvalidRuntimeSkillError
Duplicate skill name "${normalizedSkill.name}"
Error message
Duplicate skill name "${normalizedSkill.name}" What it means
Thrown by normalizeRuntimeSkills when two skills collide on `name` (compared case-insensitively, so 'Billing' and 'billing' clash). Names are user-facing identifiers and must be unique across the loaded source. The check runs after id-uniqueness, so a shared name with different ids still trips it. Wrapped as InvalidRuntimeSkillError.
Source
Thrown at packages/@n8n/agents/src/skills/registry.ts:190
return sortedSkills.map((skill) => {
const normalizedSkill: RuntimeSkill = {
...skill,
linkedFiles: normalizeLinkedFiles(skill.linkedFiles),
};
const validation = validateRuntimeSkill(normalizedSkill);
if (!validation.ok) {
throw new InvalidRuntimeSkillError(formatSkillValidationErrors(validation.errors));
}
if (seenIds.has(normalizedSkill.id)) {
throw new InvalidRuntimeSkillError(`Duplicate skill id "${normalizedSkill.id}"`);
}
seenIds.add(normalizedSkill.id);
const normalizedName = normalizedSkill.name.toLowerCase();
if (seenNames.has(normalizedName)) {
throw new InvalidRuntimeSkillError(`Duplicate skill name "${normalizedSkill.name}"`);
}
seenNames.add(normalizedName);
if (normalizedSkill.sourceDirectory) {
if (seenSourceDirectories.has(normalizedSkill.sourceDirectory)) {
throw new InvalidRuntimeSkillError(
`Duplicate skill source directory "${normalizedSkill.sourceDirectory}"`,
);
}
seenSourceDirectories.add(normalizedSkill.sourceDirectory);
}
return validation.skill;
});
}
function toRegistryEntry(skill: RuntimeSkill): RuntimeSkillRegistryEntry {
return {View on GitHub (pinned to 5ac6606e81)
Solutions
- Rename one skill's frontmatter `name` to a distinct lowercase slug (remember the check is case-insensitive).
- If both names are intentional, namespace one (e.g. `search-internal` vs `search-web`).
- Re-run to confirm the collision is resolved — note this error can mask a deeper id/source-directory duplicate, so fix all reported duplicates.
Example fix
# before — two skills both named: --- name: search --- # after --- name: search-web ---
Defensive patterns
Strategy: validation
Validate before calling
function assertUniqueSkillNames(skills: RuntimeSkill[]): void {
const seen = new Set<string>();
for (const s of skills) {
const key = s.name.toLowerCase();
if (seen.has(key)) {
throw new Error(`Duplicate skill name "${s.name}" (case-insensitive)`);
}
seen.add(key);
}
}
assertUniqueSkillNames(skills); // before createRuntimeSkillSource Type guard
function hasUniqueNames(skills: RuntimeSkill[]): boolean {
const lower = skills.map((s) => s.name.toLowerCase());
return new Set(lower).size === lower.length;
} Try / catch
try {
createRuntimeSkillSource(skills);
} catch (err) {
if (err instanceof InvalidRuntimeSkillError && /Duplicate skill name/.test(err.message)) {
// rename the colliding skill's frontmatter name, then rebuild
throw err;
}
throw err;
} Prevention
- Remember name uniqueness is case-insensitive — don't try to disambiguate by case alone.
- Namespace colliding names (e.g. search-web vs search-internal) rather than picking near-identical slugs.
- Run a uniqueness check in CI over the merged skill set from all sources.
When it happens
Trigger: Two skills with frontmatter `name: search` in different folders; a name differing only by case (`docs` vs `Docs`); importing a third-party skill pack that reuses a name already in your tree.
Common situations: Merging two skill collections that independently chose the same name; case-only differences from cross-platform filesystem moves; a vendor skill colliding with an in-house skill of the same name.
Related errors
- Duplicate skill id "${normalizedSkill.id}"
- Duplicate skill source directory "${normalizedSkill.sourceDi
- Tool name "${reservedTool.name}" is reserved for runtime ski
- Invalid skill at ${sourceDirectory}: ${formatSkillValidation
- Model ID is required
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/6c43cc87e0c228b0.
Report an issue: GitHub.