{"record":{"id":"9d528149d38b7373","repo":"Yeachan-Heo/oh-my-codex","slug":"catalog-manifest-invalid-agents-index-canonica","errorCode":null,"errorMessage":"catalog_manifest_invalid:agents[${index}].canonical","messagePattern":"catalog_manifest_invalid:agents\\[(.+?)\\]\\.canonical","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/catalog/schema.ts","lineNumber":114,"sourceCode":"    assertNonEmptyString(entry.status, `agents[${index}].status`);\n\n    if (!AGENT_CATEGORIES.has(entry.category as CatalogAgentCategory)) {\n      throw new Error(`catalog_manifest_invalid:agents[${index}].category`);\n    }\n    if (!ENTRY_STATUSES.has(entry.status as CatalogEntryStatus)) {\n      throw new Error(`catalog_manifest_invalid:agents[${index}].status`);\n    }\n\n    const name = entry.name.trim();\n    if (seenAgents.has(name)) throw new Error(`catalog_manifest_invalid:duplicate_agent:${name}`);\n    seenAgents.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:agents[${index}].canonical`);\n    }\n\n    return {\n      name,\n      category: entry.category as CatalogAgentCategory,\n      status: entry.status as CatalogEntryStatus,\n      canonical,\n    };\n  });\n\n  for (const coreSkill of REQUIRED_CORE_SKILLS) {\n    const skill = skills.find((s) => s.name === coreSkill);\n    if (!skill || skill.status !== 'active') {\n      throw new Error(`catalog_manifest_invalid:missing_core_skill:${coreSkill}`);\n    }\n  }\n\n  return {","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/catalog/schema.ts#L96-L132","documentation":"Agent entries with status alias or merged must specify a canonical target agent name; this error fires when canonical is missing or whitespace-only on such an entry. It mirrors the skills canonical requirement.","triggerScenarios":"An agents[i] entry with \"status\": \"alias\" or \"merged\" but no canonical field.","commonSituations":"Converting an agent to an alias during a rename without adding canonical, or generators dropping canonical fields.","solutions":["Add \"canonical\": \"<primary agent name>\" to the entry","Verify the canonical value names an existing agent entry","Change status back to active if the agent is not an alias"],"exampleFix":"// before\n{ \"name\": \"rev\", \"category\": \"review\", \"status\": \"alias\" }\n\n// after\n{ \"name\": \"rev\", \"category\": \"review\", \"status\": \"alias\", \"canonical\": \"reviewer\" }","handlingStrategy":"validation","validationCode":"for (const a of raw.agents) {\n  if ((a.status === 'alias' || a.status === 'merged') && !(typeof a.canonical === 'string' && a.canonical.trim())) {\n    /* add canonical or change status */\n  }\n}","typeGuard":"const hasCanonicalIfNeeded = (a: Record<string, unknown>) => !(a.status === 'alias' || a.status === 'merged') || (typeof a.canonical === 'string' && a.canonical.trim() !== '');","tryCatchPattern":null,"preventionTips":["Add canonical whenever an agent becomes an alias","Validate the manifest after renames"],"tags":["catalog","manifest","alias","validation","agents"],"backgroundTag":"missing-required-field","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}