n8n-io/n8n · error · InvalidRuntimeSkillError
Duplicate skill id "${normalizedSkill.id}"
Error message
Duplicate skill id "${normalizedSkill.id}" What it means
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.
Source
Thrown at packages/@n8n/agents/src/skills/registry.ts:184
function normalizeRuntimeSkills(skills: RuntimeSkill[]): RuntimeSkill[] {
const sortedSkills = [...skills].sort(compareRuntimeSkills);
const seenIds = new Set<string>();
const seenNames = new Set<string>();
const seenSourceDirectories = new Set<string>();
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);
}
View on GitHub (pinned to 5ac6606e81)
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.
Example fix
# before — two folders both declare: --- name: billing --- # after — rename the second one --- name: billing-enterprise ---
Defensive patterns
Strategy: validation
Validate before calling
function assertUniqueSkillIds(skills: RuntimeSkill[]): void {
const seen = new Set<string>();
for (const s of skills) {
if (seen.has(s.id)) {
throw new Error(`Duplicate skill id "${s.id}" — rename one before registering`);
}
seen.add(s.id);
}
}
assertUniqueSkillIds(skills); // before createRuntimeSkillSource Type guard
function hasUniqueIds(skills: RuntimeSkill[]): boolean {
return new Set(skills.map((s) => s.id)).size === skills.length;
} Try / catch
try {
createRuntimeSkillSource(skills);
} catch (err) {
if (err instanceof InvalidRuntimeSkillError && /Duplicate skill id/.test(err.message)) {
// dedupe or rename, then retry once — not transient, so fix data first
throw err;
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Duplicate skill name "${normalizedSkill.name}"
- 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/f535f4230e3a3e9c.
Report an issue: GitHub.