Skip to content

Routing capability evidence discards every catalog row (field-shape mismatch) #1796

Description

@lidge-jun

Client or integration

OpenCodex dashboard

Area

Catalog / models

Summary

candidateCapabilityEvidence() in src/routing/capability.ts reads the cached Codex catalog to source routing evidence, but its row filter expects field names the catalog writer never produces. Every row is discarded, so the catalog branch of the evidence chain is dead code.

cachedCatalogModels() filters on model.id and model.provider and then reads model.contextWindow / model.inputModalities. The on-disk catalog writes slug (a combined provider/id), context_window, and input_modalities. None of the four expected keys exist on any row.

This is not specific to one provider. On a live install with 17 catalog rows, zero survive the filter — native OpenAI rows, anthropic/claude-opus-5, xai/grok-4.6, all of them.

The defect is invisible for most providers because the catalog is the fourth fallback: a provider that declares modelContextWindows / modelInputModalities inline is answered by an earlier branch. It becomes visible for a model registered through ocx models add, whose capability metadata lives only in the catalog. Such a model routes as image-blind and context-unknown even though the CLI, the config, and the catalog file all show the correct values.

Reproduction

  1. Register a custom model whose provider block declares no modelContextWindows and no modelInputModalities:

    ocx models add <provider> <model> --context-window 262144 --modalities text,image
    
  2. Confirm the value reached the catalog file (~/.codex/opencodex-catalog.json):

    { "slug": "<provider>/<model>", "context_window": 262144,
      "input_modalities": ["text", "image"] }
  3. Assemble routing evidence for that candidate:

    candidateCapabilityEvidence(config, "<provider>", "<model>")
    => { tools: true, serviceTier: "unsupported", encryptedCodexTasks: false }
    

contextWindow and image are both absent.

  1. Count survivors directly:

    TOTAL: 17 | SURVIVING capability.ts filter: 0
    

Version

2.21.0

Operating system

macOS 26 (arm64)

Provider and model

Reproduced with a local llama.cpp provider (openai-chat adapter) and a custom-registered model; the 17-to-0 count covers anthropic, xai, google-antigravity, kimi, alibaba-token-plan-intl and native OpenAI rows equally.

Logs or error output

KEYS: slug,display_name,description,default_reasoning_level,supported_reasoning_levels,
shell_type,visibility,supported_in_api,priority,...,input_modalities,...,context_window,
max_context_window,auto_compact_token_limit,...,opencodex_catalog_kind
provider? undefined   id? undefined   context_window? 262144

Screenshots and supporting files

Not applicable — reproduced through direct function calls.

Redacted configuration

{ "providers": { "<provider>": { "adapter": "openai-chat",
    "baseUrl": "http://<host>:<port>/v1", "defaultModel": "<model>" } },
  "customModels": [ { "provider": "<provider>", "modelId": "<model>",
    "contextWindow": 262144, "inputModalities": ["text", "image"] } ] }

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Note on the naive fix

Reading context_window / input_modalities straight off the row is not a correct repair. ensureStrictCatalogFields() synthesizes both fields for Codex's strict parser (src/codex/catalog/parsing.ts:315 writes ["text"], :328 writes 128000), so their presence cannot distinguish a real provider assertion from a placeholder. Reading them would turn unknown into a confident image: false and a fabricated 128000, violating the module's own "unknown is not zero" contract. A correct fix needs a separate provenance channel that records only what a provider actually asserted.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcatalogModel catalog, slugs, visibility, routed entries

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions