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

catalog_manifest_invalid:agents[${index}].canonical

Error message

catalog_manifest_invalid:agents[${index}].canonical

What it means

Agent entries with status alias or merged must specify a canonical target agent name; this error fires when canonical is missing or whitespace-only on such an entry. It mirrors the skills canonical requirement.

Source

Thrown at src/catalog/schema.ts:114

    assertNonEmptyString(entry.status, `agents[${index}].status`);

    if (!AGENT_CATEGORIES.has(entry.category as CatalogAgentCategory)) {
      throw new Error(`catalog_manifest_invalid:agents[${index}].category`);
    }
    if (!ENTRY_STATUSES.has(entry.status as CatalogEntryStatus)) {
      throw new Error(`catalog_manifest_invalid:agents[${index}].status`);
    }

    const name = entry.name.trim();
    if (seenAgents.has(name)) throw new Error(`catalog_manifest_invalid:duplicate_agent:${name}`);
    seenAgents.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:agents[${index}].canonical`);
    }

    return {
      name,
      category: entry.category as CatalogAgentCategory,
      status: entry.status as CatalogEntryStatus,
      canonical,
    };
  });

  for (const coreSkill of REQUIRED_CORE_SKILLS) {
    const skill = skills.find((s) => s.name === coreSkill);
    if (!skill || skill.status !== 'active') {
      throw new Error(`catalog_manifest_invalid:missing_core_skill:${coreSkill}`);
    }
  }

  return {

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Add "canonical": "<primary agent name>" to the entry
  2. Verify the canonical value names an existing agent entry
  3. Change status back to active if the agent is not an alias

Example fix

// before
{ "name": "rev", "category": "review", "status": "alias" }

// after
{ "name": "rev", "category": "review", "status": "alias", "canonical": "reviewer" }
Defensive patterns

Strategy: validation

Validate before calling

for (const a of raw.agents) {
  if ((a.status === 'alias' || a.status === 'merged') && !(typeof a.canonical === 'string' && a.canonical.trim())) {
    /* add canonical or change status */
  }
}

Type guard

const hasCanonicalIfNeeded = (a: Record<string, unknown>) => !(a.status === 'alias' || a.status === 'merged') || (typeof a.canonical === 'string' && a.canonical.trim() !== '');

Prevention

When it happens

Trigger: An agents[i] entry with "status": "alias" or "merged" but no canonical field.

Common situations: Converting an agent to an alias during a rename without adding canonical, or generators dropping canonical fields.

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


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