{"record":{"id":"88fad2714e2af014","repo":"Yeachan-Heo/oh-my-codex","slug":"catalog-manifest-invalid-skills-index-category","errorCode":null,"errorMessage":"catalog_manifest_invalid:skills[${index}].category","messagePattern":"catalog_manifest_invalid:skills\\[(.+?)\\]\\.category","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/catalog/schema.ts","lineNumber":63,"sourceCode":"\n  if (typeof input.schemaVersion !== 'number' || !Number.isInteger(input.schemaVersion)) {\n    throw new Error('catalog_manifest_invalid:schemaVersion');\n  }\n\n  assertNonEmptyString(input.catalogVersion, 'catalogVersion');\n\n  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 {","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/catalog/schema.ts#L45-L81","documentation":"After confirming skills[i].category is a non-empty string, the validator checks it against the allowed set execution|planning|shortcut|utility. This error means the category value is not one of those four, i.e. it is a typo or a category from a newer schema.","triggerScenarios":"A skill entry with \"category\": \"exec\" or \"Execution\" (case matters) or any unknown category string.","commonSituations":"Typos, case mismatches, or manifests generated by a newer catalog schema with renamed categories.","solutions":["Change the category to one of execution, planning, shortcut, utility exactly lowercase","Check the installed library's CatalogSkillCategory type if unsure of allowed values","Regenerate the manifest with matching tool versions"],"exampleFix":"// before\n{ \"name\": \"x\", \"category\": \"Execution\", \"status\": \"active\" }\n\n// after\n{ \"name\": \"x\", \"category\": \"execution\", \"status\": \"active\" }","handlingStrategy":"validation","validationCode":"const SKILL_CATEGORIES = new Set(['execution','planning','shortcut','utility']);\nif (!raw.skills.every((s: any) => SKILL_CATEGORIES.has(s?.category))) { /* fix categories */ }","typeGuard":"function isSkillCategory(v: unknown): v is 'execution'|'planning'|'shortcut'|'utility' {\n  return typeof v === 'string' && ['execution','planning','shortcut','utility'].includes(v);\n}","tryCatchPattern":null,"preventionTips":["Use lowercase categories exactly as documented","Export/derive category unions from one shared module"],"tags":["catalog","manifest","enum","validation","skills"],"backgroundTag":"invalid-enum-value","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}