{"record":{"id":"ce5d98a7a8e61195","repo":"Yeachan-Heo/oh-my-codex","slug":"catalog-manifest-invalid-duplicate-skill-name","errorCode":null,"errorMessage":"catalog_manifest_invalid:duplicate_skill:${name}","messagePattern":"catalog_manifest_invalid:duplicate_skill:(.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/catalog/schema.ts","lineNumber":70,"sourceCode":"  if (!Array.isArray(input.skills)) throw new Error('catalog_manifest_invalid:skills');\n  if (!Array.isArray(input.agents)) throw new Error('catalog_manifest_invalid:agents');\n\n  const seenSkills = new Set<string>();\n  const skills: CatalogSkillEntry[] = input.skills.map((entry, index) => {\n    if (!isObject(entry)) throw new Error(`catalog_manifest_invalid:skills[${index}]`);\n    assertNonEmptyString(entry.name, `skills[${index}].name`);\n    assertNonEmptyString(entry.category, `skills[${index}].category`);\n    assertNonEmptyString(entry.status, `skills[${index}].status`);\n\n    if (!SKILL_CATEGORIES.has(entry.category as CatalogSkillCategory)) {\n      throw new Error(`catalog_manifest_invalid:skills[${index}].category`);\n    }\n    if (!ENTRY_STATUSES.has(entry.status as CatalogEntryStatus)) {\n      throw new Error(`catalog_manifest_invalid:skills[${index}].status`);\n    }\n\n    const name = entry.name.trim();\n    if (seenSkills.has(name)) throw new Error(`catalog_manifest_invalid:duplicate_skill:${name}`);\n    seenSkills.add(name);\n\n    const canonical = typeof entry.canonical === 'string' && entry.canonical.trim() !== ''\n      ? entry.canonical.trim()\n      : undefined;\n\n    if ((entry.status === 'alias' || entry.status === 'merged') && !canonical) {\n      throw new Error(`catalog_manifest_invalid:skills[${index}].canonical`);\n    }\n\n    return {\n      name,\n      category: entry.category as CatalogSkillCategory,\n      status: entry.status as CatalogEntryStatus,\n      canonical,\n      core: entry.core === true,\n      internalRequired: entry.internalRequired === true,\n    };","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/catalog/schema.ts#L52-L88","documentation":"Skill names must be unique after trimming; the validator tracks names in a Set and throws catalog_manifest_invalid:duplicate_skill:<name> when the same trimmed name appears twice. The duplicate name is embedded in the message.","triggerScenarios":"Two entries in skills with the same name (including whitespace-only differences, since names are compared trimmed), e.g. two \"autopilot\" entries.","commonSituations":"Merging manifests from two sources, or an entry duplicated during refactoring from alias to active status.","solutions":["Find both entries with the name in the message and remove or rename one","If one should redirect to the other, mark the extra as status \"alias\" with a distinct name and canonical pointing at the primary","Re-run the generator after deduplicating source data"],"exampleFix":"// before\n\"skills\": [ {\"name\":\"autopilot\",...}, {\"name\":\"autopilot\",...} ]\n\n// after\n\"skills\": [ {\"name\":\"autopilot\",...}, {\"name\":\"autopilot-classic\",\"status\":\"alias\",\"canonical\":\"autopilot\",\"category\":\"execution\"} ]","handlingStrategy":"validation","validationCode":"const names = raw.skills.map((s: any) => String(s.name).trim());\nif (new Set(names).size !== names.length) { /* dedupe before validation */ }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Dedupe source data before generating manifests","Use alias entries with canonical instead of duplicate names"],"tags":["catalog","manifest","duplicate","validation","skills"],"backgroundTag":"duplicate-entry","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}