Yeachan-Heo/oh-my-codex · error · Error

catalog_manifest_invalid:skills

Error message

catalog_manifest_invalid:skills[${index}].status

What it means

skills[i].status must be one of active|alias|merged|deprecated|internal. This error fires when the status string is valid-looking but not in that set. Statuses drive filtering and alias resolution, so unknown values are rejected.

Solutions

  1. Set status to one of active, alias, merged, deprecated, internal
  2. If using alias/merged, also provide the canonical field
  3. Regenerate the manifest to match the installed validator

Example fix

// before
{ "name": "x", "category": "execution", "status": "available" }

// after
{ "name": "x", "category": "execution", "status": "active" }
Defensive patterns

Strategy: validation

Validate before calling

const STATUSES = new Set(['active','alias','merged','deprecated','internal']);
if (!raw.skills.every((s: any) => STATUSES.has(s?.status))) { /* fix statuses */ }

Type guard

function isEntryStatus(v: unknown): v is 'active'|'alias'|'merged'|'deprecated'|'internal' {
  return typeof v === 'string' && ['active','alias','merged','deprecated','internal'].includes(v);
}

Prevention

When it happens

Trigger: A skill entry with status like "available", "disabled", or "Active" (case-sensitive check).

Common situations: Renamed statuses across schema versions, or hand-authored manifests using intuitive-but-wrong status words.

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/89a843cf610c2d4b. Report an issue: GitHub.

Appendix: source

Thrown at src/catalog/schema.ts:66

  }

  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 {
      name,
      category: entry.category as CatalogSkillCategory,
      status: entry.status as CatalogEntryStatus,

View on GitHub (pinned to 3ad79a8a6f)