Yeachan-Heo/oh-my-codex · error · Error
catalog_manifest_invalid:skills[${index}].category
Error message
catalog_manifest_invalid:skills[${index}].category What it means
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.
Source
Thrown at src/catalog/schema.ts:63
if (typeof input.schemaVersion !== 'number' || !Number.isInteger(input.schemaVersion)) {
throw new Error('catalog_manifest_invalid:schemaVersion');
}
assertNonEmptyString(input.catalogVersion, 'catalogVersion');
if (!Array.isArray(input.skills)) throw new Error('catalog_manifest_invalid:skills');
if (!Array.isArray(input.agents)) throw new Error('catalog_manifest_invalid:agents');
const seenSkills = new Set<string>();
const skills: CatalogSkillEntry[] = input.skills.map((entry, index) => {
if (!isObject(entry)) throw new Error(`catalog_manifest_invalid:skills[${index}]`);
assertNonEmptyString(entry.name, `skills[${index}].name`);
assertNonEmptyString(entry.category, `skills[${index}].category`);
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 {View on GitHub (pinned to 3ad79a8a6f)
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
Example fix
// before
{ "name": "x", "category": "Execution", "status": "active" }
// after
{ "name": "x", "category": "execution", "status": "active" } Defensive patterns
Strategy: validation
Validate before calling
const SKILL_CATEGORIES = new Set(['execution','planning','shortcut','utility']);
if (!raw.skills.every((s: any) => SKILL_CATEGORIES.has(s?.category))) { /* fix categories */ } Type guard
function isSkillCategory(v: unknown): v is 'execution'|'planning'|'shortcut'|'utility' {
return typeof v === 'string' && ['execution','planning','shortcut','utility'].includes(v);
} Prevention
- Use lowercase categories exactly as documented
- Export/derive category unions from one shared module
When it happens
Trigger: A skill entry with "category": "exec" or "Execution" (case matters) or any unknown category string.
Common situations: Typos, case mismatches, or manifests generated by a newer catalog schema with renamed categories.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- catalog_manifest_invalid:skills[${index}].status
- catalog_manifest_invalid:skills[${index}]
- catalog_manifest_invalid:duplicate_skill:${name}
- catalog_manifest_invalid:skills[${index}].canonical
- catalog_manifest_invalid:agents[${index}].category
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/88fad2714e2af014.
Report an issue: GitHub.