{"record":{"id":"f535f4230e3a3e9c","repo":"n8n-io/n8n","slug":"duplicate-skill-id-normalizedskill-id","errorCode":null,"errorMessage":"Duplicate skill id \"${normalizedSkill.id}\"","messagePattern":"Duplicate skill id \"(.+?)\"","errorType":"exception","errorClass":"InvalidRuntimeSkillError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/agents/src/skills/registry.ts","lineNumber":184,"sourceCode":"\nfunction normalizeRuntimeSkills(skills: RuntimeSkill[]): RuntimeSkill[] {\n\tconst sortedSkills = [...skills].sort(compareRuntimeSkills);\n\tconst seenIds = new Set<string>();\n\tconst seenNames = new Set<string>();\n\tconst seenSourceDirectories = new Set<string>();\n\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","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/agents/src/skills/registry.ts#L166-L202","documentation":"Thrown by normalizeRuntimeSkills when two loaded skills resolve to the same `id`. The skill id defaults to the frontmatter `name`, so this typically means two SKILL.md files declare the same name (or you passed two programmatic skills with identical ids). Duplicate ids would corrupt the registry's id-keyed lookup, so the loader fails fast during normalization. Wrapped as InvalidRuntimeSkillError.","triggerScenarios":"Two skill folders each containing a SKILL.md with `name: billing`; calling createRuntimeSkillSource with two RuntimeSkill objects sharing `id`; two skills in different category subdirectories that happen to use the same short name; a symlinked/copied skill folder duplicated under another path.","commonSituations":"Forking a skill and forgetting to rename it; organizing skills into category folders and reusing the leaf name; a CI workspace that mounts the same skill collection twice; a rename that left the old copy in place.","solutions":["Search the skill tree for the duplicated id/name and rename one of the frontmatter `name` fields to a unique lowercase slug.","Remove the stale duplicate folder if one is a leftover copy.","If ids are set programmatically, ensure each RuntimeSkill.id is unique before calling createRuntimeSkillSource."],"exampleFix":"# before — two folders both declare:\n---\nname: billing\n---\n\n# after — rename the second one\n---\nname: billing-enterprise\n---","handlingStrategy":"validation","validationCode":"function assertUniqueSkillIds(skills: RuntimeSkill[]): void {\n  const seen = new Set<string>();\n  for (const s of skills) {\n    if (seen.has(s.id)) {\n      throw new Error(`Duplicate skill id \"${s.id}\" — rename one before registering`);\n    }\n    seen.add(s.id);\n  }\n}\n\nassertUniqueSkillIds(skills); // before createRuntimeSkillSource","typeGuard":"function hasUniqueIds(skills: RuntimeSkill[]): boolean {\n  return new Set(skills.map((s) => s.id)).size === skills.length;\n}","tryCatchPattern":"try {\n  createRuntimeSkillSource(skills);\n} catch (err) {\n  if (err instanceof InvalidRuntimeSkillError && /Duplicate skill id/.test(err.message)) {\n    // dedupe or rename, then retry once — not transient, so fix data first\n    throw err;\n  }\n  throw err;\n}","preventionTips":["Make the frontmatter `name` unique across the whole skills tree (it becomes the id by default).","Add a pre-commit/CI check that asserts skill ids/names are unique across the repo.","When merging two skill collections, run an id-collision report before combining."],"tags":["skills","duplicates","configuration"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}