Yeachan-Heo/oh-my-codex · error · Error
catalog_manifest_invalid:skills[${index}].canonical
Error message
catalog_manifest_invalid:skills[${index}].canonical What it means
Entries whose status is alias or merged must declare a canonical skill name they redirect to; this error fires when such an entry has no canonical (or a whitespace-only one). Without canonical, alias/merged entries cannot be resolved to their target.
Source
Thrown at src/catalog/schema.ts:78
assertNonEmptyString(entry.status, `skills[${index}].status`);
if (!SKILL_CATEGORIES.has(entry.category as CatalogSkillCategory)) {
throw new Error(`catalog_manifest_invalid:skills[${index}].category`);
}
if (!ENTRY_STATUSES.has(entry.status as CatalogEntryStatus)) {
throw new Error(`catalog_manifest_invalid:skills[${index}].status`);
}
const name = entry.name.trim();
if (seenSkills.has(name)) throw new Error(`catalog_manifest_invalid:duplicate_skill:${name}`);
seenSkills.add(name);
const canonical = typeof entry.canonical === 'string' && entry.canonical.trim() !== ''
? entry.canonical.trim()
: undefined;
if ((entry.status === 'alias' || entry.status === 'merged') && !canonical) {
throw new Error(`catalog_manifest_invalid:skills[${index}].canonical`);
}
return {
name,
category: entry.category as CatalogSkillCategory,
status: entry.status as CatalogEntryStatus,
canonical,
core: entry.core === true,
internalRequired: entry.internalRequired === true,
};
});
const seenAgents = new Set<string>();
const agents: CatalogAgentEntry[] = input.agents.map((entry, index) => {
if (!isObject(entry)) throw new Error(`catalog_manifest_invalid:agents[${index}]`);
assertNonEmptyString(entry.name, `agents[${index}].name`);
assertNonEmptyString(entry.category, `agents[${index}].category`);
assertNonEmptyString(entry.status, `agents[${index}].status`);View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Add "canonical": "<primary skill name>" to the entry
- Ensure the canonical name matches an existing non-alias skill name in the manifest
- If the entry should stand alone, change status back to active
Example fix
// before
{ "name": "ap", "category": "execution", "status": "alias" }
// after
{ "name": "ap", "category": "execution", "status": "alias", "canonical": "autopilot" } Defensive patterns
Strategy: validation
Validate before calling
for (const s of raw.skills) {
if ((s.status === 'alias' || s.status === 'merged') && !(typeof s.canonical === 'string' && s.canonical.trim())) {
/* add canonical or change status */
}
} Type guard
const hasCanonicalIfNeeded = (s: Record<string, unknown>) => !(s.status === 'alias' || s.status === 'merged') || (typeof s.canonical === 'string' && s.canonical.trim() !== '');
Prevention
- When renaming a skill, immediately add canonical to the alias entry
- Run validateCatalogManifest after every manifest edit
When it happens
Trigger: A skills[i] entry with "status": "alias" or "merged" but missing the canonical field, or canonical: "" / " ".
Common situations: Renaming a skill to alias status and forgetting to add canonical, or generators that drop canonical when it equals the entry name.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- catalog_manifest_invalid:skills[${index}]
- catalog_manifest_invalid:skills[${index}].category
- catalog_manifest_invalid:skills[${index}].status
- catalog_manifest_invalid:duplicate_skill:${name}
- catalog_manifest_invalid:agents[${index}].canonical
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/6f861e6024169f17.
Report an issue: GitHub.