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
- Filter
"video" at the provider-metadata source.
- 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.
- Regression test that an out-of-enum value cannot reach a written catalog by
any path, including preserveExactInputModalities.
- 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.
Summary
A provider that advertises
"video"in a model'sinput_modalitiesmakes theCodex config loader reject the entire
opencodex-catalog.json. Codex parsesthat field as a closed enum (
text | image | audio), so one out-of-enum valuetakes down every plugin, app and MCP server, not just the offending model.
Observed on
zenmux/meta-muse-spark-1.1: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()insrc/codex/catalog/provider-fetch.tsaccepted"video"in its filter, so provider metadata carried it straight into theserialized 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 thevision-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
"video"at the provider-metadata source.input_modalitiesto the enum at the single choke point every entrypasses through (
ensureStrictCatalogFields), falling back to["text"]ifnothing acceptable survives — an entry with no modality at all is worse than
a text-only one.
any path, including
preserveExactInputModalities.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 videoreasoning intentionally unchanged).
the enum.