You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Catalog: a provider-advertised "video" input modality makes Codex reject the whole catalog #759
A provider that advertises "video" in a model's input_modalities makes the
Codex config loader reject the entireopencodex-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.
Closing the loop on scope. The two landed fixes cover entry construction: "video" is dropped from the provider-metadata filter, and ensureStrictCatalogFields normalizes the field at the single point every constructed entry passes through.
An adversarial review of the follow-up plan removed two items I had proposed, both on evidence rather than effort:
Read-side auto-repair — rejected.readCatalog also serves pristine backups and models_cache.json, so repairing in place there would mutate recovery evidence and user-owned files during mere inspection, and it still could not fix the Codex app until some opencodex command ran. ocx sync is the explicit repair route; it already normalizes every outgoing entry.
A blanket enum sanitizer — rejected. There are other closed enums (visibility, shell_type, web_search_tool_type), but truncation_policy.mode legitimately carries bothbytes and tokens in the upstream snapshot, so a sanitizer hardcoding one value would corrupt valid entries. Those fields also never receive provider data: their only writer is parsing.ts with hardcoded valid values. input_modalities is the one enum field that carries a provider-supplied string through to disk.
I also had one claim wrong and it is worth recording: I thought the backup-restore path could rewrite the poisoned value. It cannot — writePristineCatalogBackup refuses to back up any catalog containing routed slugs, and the offending model is routed, so it never enters a backup.
What was actually missing is now covered: a row already poisoned on disk. With no provider configured, sync deliberately preserves stale routed entries, so that row is read back, merged and rewritten — it has to be repaired on the way out or the same rejection repeats. The new test seeds zenmux/meta-muse-spark-1.1 with ["text","image","video"], runs real syncCatalogModels() against an isolated CODEX_HOME, and asserts the slug survives with modalities exactly ["text","image"]. Asserting only "no video in the output" would pass just as happily if a future sync dropped the row instead of cleaning it, silently deleting a provider model.
Detection power was verified, not assumed: removing the enum filter makes the test fail, restoring it makes it pass.
Verification: 142 pass across the five catalog suites, including the 12 vision-sidecar cases that keep internal video reasoning for the xAI bridge intact. The reporting user's catalog now parses with 31 models, zero out-of-enum values, and the offending model at ["text","image","audio"].
Remaining known gap, deliberately not closed here: Codex's parse error does not name ocx sync, so a user hitting this has no recovery hint. That is a CLI/docs diagnostic decision.
Claim: A provider-advertised videoinput_modalities value makes Codex reject the entire opencodex-catalog.json. Checked against: dev@be177ea5
Field
Finding
Still an issue on latest development?
yes
Exact issue
modelInputModalities still accepts video; ensureStrictCatalogFields does not strip out-of-enum modalities
Fixed on development?
no on tip — despite an owner comment describing landed construction-path filters, those changes are not present on origin/dev
Open PR covering this?
none found
Duplicate of?
none
Security relevance
none — n/a
Priority
high — one bad modality takes down apps/MCP for the whole catalog
Verdict
Needs fix on development
Evidence:src/codex/catalog/provider-fetch.ts:327 still filters text|image|audio|video. ensureStrictCatalogFields (parsing.ts ~274–276) only default-fills when the field is missing, does not sanitize enum members. No tests/catalog-input-modality-enum.test.ts on tip. Vision-sidecar tests still expect internal video reasoning. Gaps: Owner comment may describe local/unpushed work; reconcile before closing.
The video modality can no longer reach the catalog. Three write paths filter it independently:
src/codex/catalog/provider-fetch.ts:327-333 — filters provider-advertised modalities to text | image | audio at fetch time, with the reason recorded inline: Codex parses input_modalities as a closed enum, so one model's stray value rejected the whole file.
src/codex/catalog/parsing.ts:281-285 — sanitizes again on parse.
src/codex/catalog/sync.ts:451-458 — applies the same filter to rows preserved from disk, so an entry written by an older build cannot survive a sync.
That last one matters: filtering only at fetch would have left already-written catalogs broken.
Closing as fixed. If a provider advertises a modality that still breaks loading, please reopen with the provider and the raw /v1/models row.
Activity
github-actions commentedon Jul 30, 2026
Automated translation bookkeeping — detected language: unknown.
lidge-jun commentedon Jul 30, 2026
Closing the loop on scope. The two landed fixes cover entry construction:
"video"is dropped from the provider-metadata filter, andensureStrictCatalogFieldsnormalizes the field at the single point every constructed entry passes through.An adversarial review of the follow-up plan removed two items I had proposed, both on evidence rather than effort:
Read-side auto-repair — rejected.
readCatalogalso serves pristine backups andmodels_cache.json, so repairing in place there would mutate recovery evidence and user-owned files during mere inspection, and it still could not fix the Codex app until some opencodex command ran.ocx syncis the explicit repair route; it already normalizes every outgoing entry.A blanket enum sanitizer — rejected. There are other closed enums (
visibility,shell_type,web_search_tool_type), buttruncation_policy.modelegitimately carries bothbytesandtokensin the upstream snapshot, so a sanitizer hardcoding one value would corrupt valid entries. Those fields also never receive provider data: their only writer isparsing.tswith hardcoded valid values.input_modalitiesis the one enum field that carries a provider-supplied string through to disk.I also had one claim wrong and it is worth recording: I thought the backup-restore path could rewrite the poisoned value. It cannot —
writePristineCatalogBackuprefuses to back up any catalog containing routed slugs, and the offending model is routed, so it never enters a backup.What was actually missing is now covered: a row already poisoned on disk. With no provider configured, sync deliberately preserves stale routed entries, so that row is read back, merged and rewritten — it has to be repaired on the way out or the same rejection repeats. The new test seeds
zenmux/meta-muse-spark-1.1with["text","image","video"], runs realsyncCatalogModels()against an isolatedCODEX_HOME, and asserts the slug survives with modalities exactly["text","image"]. Asserting only "novideoin the output" would pass just as happily if a future sync dropped the row instead of cleaning it, silently deleting a provider model.Detection power was verified, not assumed: removing the enum filter makes the test fail, restoring it makes it pass.
Verification: 142 pass across the five catalog suites, including the 12 vision-sidecar cases that keep internal video reasoning for the xAI bridge intact. The reporting user's catalog now parses with 31 models, zero out-of-enum values, and the offending model at
["text","image","audio"].Remaining known gap, deliberately not closed here: Codex's parse error does not name
ocx sync, so a user hitting this has no recovery hint. That is a CLI/docs diagnostic decision.Wibias commentedon Jul 30, 2026
[shipping-github] Research review
Claim: A provider-advertised
videoinput_modalitiesvalue makes Codex reject the entireopencodex-catalog.json.Checked against: dev@be177ea5
modelInputModalitiesstill acceptsvideo;ensureStrictCatalogFieldsdoes not strip out-of-enum modalitiesorigin/devEvidence:
src/codex/catalog/provider-fetch.ts:327still filterstext|image|audio|video.ensureStrictCatalogFields(parsing.ts~274–276) only default-fills when the field is missing, does not sanitize enum members. Notests/catalog-input-modality-enum.test.tson tip. Vision-sidecar tests still expect internal video reasoning.Gaps: Owner comment may describe local/unpushed work; reconcile before closing.
Wibias commentedon Jul 30, 2026
[shipping-github] Opened PR #777 to address this.
fix(gui): close the WP3 audit blockers and stop the startup chip wait…
test(catalog): pin the #759 incident sequence at the sync write boundary
docs(plan): record wp8, including the claim the audit refuted
docs(cli): document every per-model ocx models operation
fix(api): reject out-of-enum input modalities at the custom-model routes
fix(api): reject non-string input modalities instead of filtering the…
test(api): make the modality clear-path test actually prove clearing
docs(plan): record wp7 as PARTIAL and what the doc audit uncovered
lidge-jun commentedon Jul 31, 2026
Fixed on
dev(verified atbf9bc1ac8).The
videomodality can no longer reach the catalog. Three write paths filter it independently:src/codex/catalog/provider-fetch.ts:327-333— filters provider-advertised modalities totext | image | audioat fetch time, with the reason recorded inline: Codex parsesinput_modalitiesas a closed enum, so one model's stray value rejected the whole file.src/codex/catalog/parsing.ts:281-285— sanitizes again on parse.src/codex/catalog/sync.ts:451-458— applies the same filter to rows preserved from disk, so an entry written by an older build cannot survive a sync.That last one matters: filtering only at fetch would have left already-written catalogs broken.
Closing as fixed. If a provider advertises a modality that still breaks loading, please reopen with the provider and the raw
/v1/modelsrow.docs(plan): disposition all 41 open issues against dev head
fix(gui): close the WP3 audit blockers and stop the startup chip wait…
test(catalog): pin the lidge-jun#759 incident sequence at the sync wr…
docs(plan): record wp8, including the claim the audit refuted
docs(cli): document every per-model ocx models operation
fix(api): reject out-of-enum input modalities at the custom-model routes
fix(api): reject non-string input modalities instead of filtering the…
test(api): make the modality clear-path test actually prove clearing
docs(plan): record wp7 as PARTIAL and what the doc audit uncovered
docs(plan): disposition all 41 open issues against dev head