Skip to content

Catalog: a provider-advertised "video" input modality makes Codex reject the whole catalog #759

Description

@lidge-jun

Summary

A provider that advertises "video" in a model's input_modalities makes the
Codex config loader reject the entire opencodex-catalog.json. Codex parses
that field as a closed enum (text | image | audio), so one out-of-enum value
takes down every plugin, app and MCP server, not just the offending model.

Observed on zenmux/meta-muse-spark-1.1:

failed to reload config: failed to parse model_catalog_json path
`/Users/…/.codex/opencodex-catalog.json` as JSON:
unknown variant `video`, expected one of `text`, `image`, `audio` at line 2013 column 15

The Codex app surface shows "Unable to load apps" with 0 apps and 0 MCPs.
Nothing in the message points at the model or at opencodex, so the failure reads
as a Codex problem rather than a catalog one.

Why it happened

modelInputModalities() in src/codex/catalog/provider-fetch.ts accepted
"video" in its filter, so provider metadata carried it straight into the
serialized catalog. There was also no normalization at the point where every
entry is finalized, so jawcode metadata and effort sync could reintroduce an
out-of-enum value independently of the provider path.

Note that "video" is legitimate internally: the xAI video bridge
(images.videoBridgeEnabled, src/images/xai-video-client.ts) and the
vision-sidecar modality plumbing both reason about video. The defect is
specifically that the value escapes into the Codex-facing catalog file, whose
schema does not have it.

Scope of the fix

  1. Filter "video" at the provider-metadata source.
  2. Normalize input_modalities to the enum at the single choke point every entry
    passes through (ensureStrictCatalogFields), falling back to ["text"] if
    nothing acceptable survives — an entry with no modality at all is worse than
    a text-only one.
  3. Regression test that an out-of-enum value cannot reach a written catalog by
    any path, including preserveExactInputModalities.
  4. Sweep the remaining serialization boundaries (aggregation/combos, custom rows,
    the management API's catalog write path) for other unvalidated enum fields, and
    repair an already-poisoned catalog on read instead of failing closed.

Item 4 is the part that still needs work: the current fix proves the two known
write paths are clean, not that every field Codex treats as an enum is validated.

Verification

  • tests/catalog-input-modality-enum.test.ts — 5 pass.
  • tests/catalog-vision-sidecar-modalities.test.ts — 12 pass (internal video
    reasoning intentionally unchanged).
  • A user catalog that previously failed to parse now loads with 0 models outside
    the enum.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions