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

catalog_manifest_invalid:duplicate_agent

Error message

catalog_manifest_invalid:duplicate_agent:${name}

What it means

Agent names must be unique after trimming; when the same trimmed name appears twice in agents, the validator throws catalog_manifest_invalid:duplicate_agent:<name> with the duplicate name embedded. This prevents ambiguous agent resolution.

Solutions

  1. Locate both entries with the name in the message and remove or rename one
  2. Represent the extra as a distinct alias name with canonical pointing at the primary
  3. Deduplicate the source data and regenerate

Example fix

// before
"agents": [ {"name":"builder",...}, {"name":"builder",...} ]

// after
"agents": [ {"name":"builder",...}, {"name":"build-agent","status":"alias","canonical":"builder","category":"build"} ]
Defensive patterns

Strategy: validation

Validate before calling

const names = raw.agents.map((a: any) => String(a.name).trim());
if (new Set(names).size !== names.length) { /* dedupe before validation */ }

Prevention

When it happens

Trigger: Two agent entries with the same name (whitespace differences count as equal since comparison is on trimmed names).

Common situations: Concatenating manifests from multiple teams/sources, or duplicates introduced during category reorganization.

Related errors


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

Appendix: source

Thrown at src/catalog/schema.ts:106

    };
  });

  const seenAgents = new Set<string>();
  const agents: CatalogAgentEntry[] = input.agents.map((entry, index) => {
    if (!isObject(entry)) throw new Error(`catalog_manifest_invalid:agents[${index}]`);
    assertNonEmptyString(entry.name, `agents[${index}].name`);
    assertNonEmptyString(entry.category, `agents[${index}].category`);
    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,
    };
  });

View on GitHub (pinned to 3ad79a8a6f)