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

catalog_manifest_invalid:root

Error message

catalog_manifest_invalid:root

What it means

validateCatalogManifest requires the parsed manifest JSON to be a plain object at the top level; catalog_manifest_invalid:root means the input was an array, a primitive, or null. It fires before any field checks and indicates the file is structurally not a manifest at all.

Source

Thrown at src/catalog/schema.ts:44

}

const SKILL_CATEGORIES = new Set<CatalogSkillCategory>(['execution', 'planning', 'shortcut', 'utility']);
const AGENT_CATEGORIES = new Set<CatalogAgentCategory>(['build', 'review', 'domain', 'product', 'coordination']);
const ENTRY_STATUSES = new Set<CatalogEntryStatus>(['active', 'alias', 'merged', 'deprecated', 'internal']);
const REQUIRED_CORE_SKILLS = new Set(['autopilot', 'ralplan', 'team', 'ultragoal']);

function isObject(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

function assertNonEmptyString(value: unknown, field: string): asserts value is string {
  if (typeof value !== 'string' || value.trim() === '') {
    throw new Error(`catalog_manifest_invalid:${field}`);
  }
}

export function validateCatalogManifest(input: unknown): CatalogManifest {
  if (!isObject(input)) throw new Error('catalog_manifest_invalid:root');

  if (typeof input.schemaVersion !== 'number' || !Number.isInteger(input.schemaVersion)) {
    throw new Error('catalog_manifest_invalid:schemaVersion');
  }

  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)) {

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Open the resolved manifest path and confirm the top level is { ... } with schemaVersion, catalogVersion, skills, agents
  2. If the file is wrong/truncated, regenerate or restore it from source control
  3. Check that only one of templates/src/dist manifest files exists and it is the intended one

Example fix

// before
[{ "name": "autopilot" }]

// after
{ "schemaVersion": 1, "catalogVersion": "1.0.0", "skills": [...], "agents": [...] }
Defensive patterns

Strategy: validation

Validate before calling

const raw: unknown = JSON.parse(text);
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) { /* reject before calling reader */ }

Type guard

function isPlainObject(v: unknown): v is Record<string, unknown> { return typeof v === 'object' && v !== null && !Array.isArray(v); }

Prevention

When it happens

Trigger: readCatalogManifest loading a manifest file whose JSON root is an array (e.g. a list of entries), a bare string/number, or 'null'.

Common situations: Manifest file replaced with a different JSON document, a truncated write leaving invalid content that still parses, or pointing resolveManifestPath at the wrong file.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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