Yeachan-Heo/oh-my-codex · error · Error
catalog_manifest_invalid:duplicate_skill:${name}
Error message
catalog_manifest_invalid:duplicate_skill:${name} What it means
Skill names must be unique after trimming; the validator tracks names in a Set and throws catalog_manifest_invalid:duplicate_skill:<name> when the same trimmed name appears twice. The duplicate name is embedded in the message.
Source
Thrown at src/catalog/schema.ts:70
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,
canonical,
core: entry.core === true,
internalRequired: entry.internalRequired === true,
};View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Find both entries with the name in the message and remove or rename one
- If one should redirect to the other, mark the extra as status "alias" with a distinct name and canonical pointing at the primary
- Re-run the generator after deduplicating source data
Example fix
// before
"skills": [ {"name":"autopilot",...}, {"name":"autopilot",...} ]
// after
"skills": [ {"name":"autopilot",...}, {"name":"autopilot-classic","status":"alias","canonical":"autopilot","category":"execution"} ] Defensive patterns
Strategy: validation
Validate before calling
const names = raw.skills.map((s: any) => String(s.name).trim());
if (new Set(names).size !== names.length) { /* dedupe before validation */ } Prevention
- Dedupe source data before generating manifests
- Use alias entries with canonical instead of duplicate names
When it happens
Trigger: Two entries in skills with the same name (including whitespace-only differences, since names are compared trimmed), e.g. two "autopilot" entries.
Common situations: Merging manifests from two sources, or an entry duplicated during refactoring from alias to active status.
Related errors
- catalog_manifest_invalid:skills[${index}]
- catalog_manifest_invalid:skills[${index}].category
- catalog_manifest_invalid:skills[${index}].status
- catalog_manifest_invalid:skills[${index}].canonical
- catalog_manifest_invalid:duplicate_agent:${name}
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/ce5d98a7a8e61195.
Report an issue: GitHub.