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

  1. Change the category to one of execution, planning, shortcut, utility exactly lowercase
  2. Check the installed library's CatalogSkillCategory type if unsure of allowed values
  3. 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

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


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/88fad2714e2af014. Report an issue: GitHub.