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

catalog_manifest_invalid:skills[${index}].status

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.

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)

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.