{"record":{"id":"6c43cc87e0c228b0","repo":"n8n-io/n8n","slug":"duplicate-skill-name-normalizedskill-name","errorCode":null,"errorMessage":"Duplicate skill name \"${normalizedSkill.name}\"","messagePattern":"Duplicate skill name \"(.+?)\"","errorType":"exception","errorClass":"InvalidRuntimeSkillError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/agents/src/skills/registry.ts","lineNumber":190,"sourceCode":"\n\treturn sortedSkills.map((skill) => {\n\t\tconst normalizedSkill: RuntimeSkill = {\n\t\t\t...skill,\n\t\t\tlinkedFiles: normalizeLinkedFiles(skill.linkedFiles),\n\t\t};\n\t\tconst validation = validateRuntimeSkill(normalizedSkill);\n\t\tif (!validation.ok) {\n\t\t\tthrow new InvalidRuntimeSkillError(formatSkillValidationErrors(validation.errors));\n\t\t}\n\n\t\tif (seenIds.has(normalizedSkill.id)) {\n\t\t\tthrow new InvalidRuntimeSkillError(`Duplicate skill id \"${normalizedSkill.id}\"`);\n\t\t}\n\t\tseenIds.add(normalizedSkill.id);\n\n\t\tconst normalizedName = normalizedSkill.name.toLowerCase();\n\t\tif (seenNames.has(normalizedName)) {\n\t\t\tthrow new InvalidRuntimeSkillError(`Duplicate skill name \"${normalizedSkill.name}\"`);\n\t\t}\n\t\tseenNames.add(normalizedName);\n\n\t\tif (normalizedSkill.sourceDirectory) {\n\t\t\tif (seenSourceDirectories.has(normalizedSkill.sourceDirectory)) {\n\t\t\t\tthrow new InvalidRuntimeSkillError(\n\t\t\t\t\t`Duplicate skill source directory \"${normalizedSkill.sourceDirectory}\"`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tseenSourceDirectories.add(normalizedSkill.sourceDirectory);\n\t\t}\n\n\t\treturn validation.skill;\n\t});\n}\n\nfunction toRegistryEntry(skill: RuntimeSkill): RuntimeSkillRegistryEntry {\n\treturn {","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/agents/src/skills/registry.ts#L172-L208","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before — two skills both named:\n---\nname: search\n---\n\n# after\n---\nname: search-web\n---","handlingStrategy":"validation","validationCode":"function assertUniqueSkillNames(skills: RuntimeSkill[]): void {\n  const seen = new Set<string>();\n  for (const s of skills) {\n    const key = s.name.toLowerCase();\n    if (seen.has(key)) {\n      throw new Error(`Duplicate skill name \"${s.name}\" (case-insensitive)`);\n    }\n    seen.add(key);\n  }\n}\n\nassertUniqueSkillNames(skills); // before createRuntimeSkillSource","typeGuard":"function hasUniqueNames(skills: RuntimeSkill[]): boolean {\n  const lower = skills.map((s) => s.name.toLowerCase());\n  return new Set(lower).size === lower.length;\n}","tryCatchPattern":"try {\n  createRuntimeSkillSource(skills);\n} catch (err) {\n  if (err instanceof InvalidRuntimeSkillError && /Duplicate skill name/.test(err.message)) {\n    // rename the colliding skill's frontmatter name, then rebuild\n    throw err;\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["skills","duplicates","configuration"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}