BlogAI Engineering
AI Engineering16 min read· July 9, 2026

Codex Desktop Hides Your Custom Models

Carolina Fogliato

Published July 9, 2026

Your model runs from the CLI but the Desktop picker acts like it doesn't exist. Five fixes for the #19694 filter bug, the catalog gap, and the cc-switch v3.16.5 patch — and what it teaches about trusting tools you don't control.

You wired up a custom provider, codex runs fine from the terminal, but the Desktop model picker acts like your models do not exist.

That gap is almost never your API key or your config syntax. It is the Desktop client quietly filtering models the backend already loaded. The requests work. The dropdown just refuses to show them.

This guide does two things. First: the concrete fixes, ordered from a one-line workaround to a clean, switchable picker. Second — the part most guides skip — what a UI bug in a tool you depend on actually teaches you about how your AI stack is wired.

If you'd rather have someone harden the whole layer for you, we do that too.


30-SECOND DIAGNOSIS

Match your symptom before you edit anything

Most people are one config line away from a fix. Match what you see to the table, then act.

What you seeWhat is actually wrongFirst move
Picker shows only "Custom", no model nameModel set inline, no catalog metadataFix 1: keep the inline model — it works
codex CLI /model lists it, Desktop does notDesktop client-side filter (issue #19694)Fix 1 now, Fix 4 for a real listing
Picker dropdown is emptyMissing or malformed catalogFix 2 or Fix 4
Nothing loads, startup prints a warningProvider in project-local .codex/config.tomlFix 3: move it to ~/.codex/
cc-switch native provider, still hiddenPre-v3.16.5 catalog formatFix 4: update, then re-save

The single fastest check: open a terminal in the same folder, run codex (the CLI), then type /model. If the CLI lists your model but Desktop does not, you have the client-side filter bug — and no amount of config editing on the Desktop side will surface it. Skip to Fix 1.


FIRST DECISION

Fix the display, or fix the route?

This is the decision that saves you an afternoon of editing the wrong layer.

Fix the picker (Fixes 1–4) when

  • Your requests succeed from the CLI or a direct curl, and the model just does not appear in the Desktop dropdown.
  • You are looking at the #19694 client-side filter or a catalog gap — display problems, not routing ones.
  • You want a clean, switchable model list for a team that swaps models often.

Fix the route (not the picker) when

  • curl against your gateway comes back 401, 404, or model-not-found. The provider or model string is wrong.
  • Every request 404s because wire_api is set to the removed chat path or the gateway has no /responses endpoint.
  • The model runs but returns the wrong output — your catalog slug does not match the provider's model ID.

Stop rule. If your only goal is to send requests to a custom model, Fix 1 alone gets you there: set model inline, restart, done. The rest is about making the picker show a clean, switchable list. If you never open the dropdown, stop after Fix 1.


ROOT CAUSES

Four reasons the picker hides your model

They need different fixes. Picking the wrong one is why people spend an afternoon re-typing a working config.

Root causeWhy the picker hides the modelWhich setups hit itFix
Desktop client-side filterApp-server returns the model via model/list; the Desktop renderer drops it.Any custom provider with a catalogFix 1 + Fix 4
No catalog metadatamodel set inline, picker has nothing to describe.Minimal config.toml setupsFix 1 or Fix 2
Malformed / stale catalogCatalog written in a format Codex will not enumerate.Older cc-switch, hand-edited JSONFix 2 / Fix 4
Project-local providermodel_provider ignored outside ~/.codex/config.toml..codex/config.toml in a repoFix 3

The first one surprises people. Per the open codex issue #19694, the app-server loads the catalog correctly and returns your models through its model/list endpoint — but the Desktop UI applies an extra filter and drops locally configured models before rendering the picker. The backend knows about your model. The frontend refuses to show it. That is a client bug, not a config error, and it was filed on 2026-04-26 and is still open at time of writing.

The second cause is the most common and the least alarming. If you only wrote model = "some-id" with no catalog, Codex has the string but no display name, no context window, no capability flags. The picker shows the label "Custom" and moves on. Your requests still go to the right model. This trips people up because "Custom" looks broken when it is actually working.


HOW CODEX LOADS MODELS

Where the Desktop diverges from the CLI

Codex builds its model list in layers, and the order tells you which fix will actually land.

Three sources feed the list: a catalog bundled with the binary, a remote catalog fetched from OpenAI, and your local model_catalog_json. The local file wins — it overrides both the bundled and the remote catalog on startup, which is why a correct model_catalog_json is the real lever for third-party models rather than a nice-to-have.

Here is the part that trips everyone. When Codex starts, the app-server reads all three layers, resolves them, and exposes the result through an internal model/list endpoint. The CLI renders that list directly, so custom models show up. The Desktop app renders the same list through an extra client step that, per issue #19694, applies its own allowlist and drops locally configured entries before they reach the dropdown. Same backend, same catalog, two different pickers. That single divergence is why the CLI is a dependable escape hatch and Desktop is not.

There is also a cache to watch. Codex keeps ~/.codex/models_cache.json, and after you switch providers or edit a catalog it does not always resync. A stale cache can keep showing an old list — or an empty one — even after your config is correct. Deleting that file forces a clean rebuild on the next launch, which is worth trying before you assume the catalog itself is wrong.


THE FIXES

How to make Codex Desktop see your models

Work top to bottom. Fix 1 always works. The later fixes make the picker pretty and the list switchable.

THE RELIABLE WORKAROUND

Fix 1: Set the model inline (the reliable workaround)

This is the workaround the #19694 thread converged on, and the one to reach for first. Put the model string directly in config.toml and let the picker say "Custom".

# ~/.codex/config.toml   (user-level, not a project folder)
model = "your-provider/your-model-id"
model_provider = "my-gateway"

[model_providers.my-gateway]
name = "my-gateway"
base_url = "https://your-gateway.example/v1"
env_key = "MY_GATEWAY_API_KEY"
wire_api = "responses"

Export the key (export MY_GATEWAY_API_KEY=sk-... in your shell profile), fully quit Codex Desktop, and reopen it. The picker will read "Custom" but every request goes to the model you set. Swap the model string to switch models — no new provider block each time.

Note on wire_api: Codex removed its older chat/completions path in early February 2026 (discussion #7782); responses is now the only supported value and the default when the key is omitted. A provider still set to wire_api = "chat" fails on startup. Your gateway must expose an OpenAI-compatible Responses endpoint at {base_url}/responses. Point Codex at a gateway that only speaks /chat/completions and every request 404s — which looks like a model problem but is a protocol mismatch.

MAKE THE PICKER LIST IT

Fix 2: Add a model catalog so the picker lists it

If you want a real name in the dropdown instead of "Custom", you need a model_catalog_json. Point config.toml at a JSON file with a top-level models array; each entry describes one model.

# top of ~/.codex/config.toml
model_catalog_json = "/Users/you/.codex/my-models.json"
model = "your-provider/your-model-id"
model_provider = "my-gateway"
{
  "models": [
    {
      "slug": "your-provider/your-model-id",
      "display_name": "Your Model (gateway)",
      "description": "Coding model via the gateway",
      "context_window": 262000,
      "max_context_window": 262000,
      "supported_in_api": true,
      "visibility": "list",
      "priority": 1
    }
  ]
}

The slug must equal the string you send to the provider. The official catalog carries many more fields per model (reasoning levels, tool types, modality flags), and hand-authoring the full shape is fiddly — which is exactly why Fix 4 exists. Restart Desktop after any catalog change. This layer overrides both the bundled and the remote catalog, and it only re-reads on startup, so a per-thread config change will not reapply it.

FIX THE CONFIG LAYER

Fix 3: Move the provider to the user-level config

If Codex prints a startup warning about ignored provider settings, your provider block is in the wrong file. model_provider and model_providers only work in ~/.codex/config.toml. A repo's .codex/config.toml cannot define providers.

# check where your provider actually lives
grep -rn "model_providers" ~/.codex/config.toml ./.codex/config.toml 2>/dev/null
# if it is in ./.codex/config.toml, move the [model_providers.*] block
# and model_provider = "..." into ~/.codex/config.toml, then restart

Keep repo-specific things like instructions files in the project config. Keep the provider and the model catalog user-level. One caveat: project-local model_catalog_json is a separate case — by design it should still be read, but on fresh Desktop threads it currently isn't, which is an open bug (#26308) rather than intended behavior.

FIX THE TOOLING LAYER

Fix 4: Let cc-switch v3.16.5 generate the catalog

If you manage Codex through cc-switch, update to v3.16.5 or later. That release is the one that fixes the empty picker for native providers. It generates ~/.codex/cc-switch-model-catalog.json for suppliers on native Responses endpoints (apiFormat: "openai_responses"), which is what lets Codex Desktop actually see the models and use built-in tools.

  • Re-save each native provider once. The catalog is only regenerated on save. Existing providers keep their old (broken) state until you open and re-save them. There is no automatic migration.
  • Understand the decoupling. In v3.16.5 the model catalog no longer rides on the "local routing" toggle. Native Responses suppliers now generate the catalog whether or not local routing is on, while Chat-format suppliers keep going through proxy conversion as before.
# confirm the catalog exists and lists your models after re-saving
cat ~/.codex/cc-switch-model-catalog.json | grep -o '"slug":[^,]*'
# if this is empty, re-save the provider in cc-switch and check again

One caveat cc-switch itself disables: a few first-party Chinese models (MiMo, LongCat, MiniMax, Qwen3-Coder) do not support OpenAI's built-in web_search on their gateways, so v3.16.5 turns that tool off for them by default to stop Codex throwing a hard 400. If you route those through a gateway, expect web search to be off.


ERRORS THAT MIMIC THE PICKER BUG

Rule these out before you touch the catalog

Half the "custom models not showing" reports are a broken route wearing a display costume. Each one produces a symptom that looks like the picker hiding your model, when the request never had a chance to land.

SymptomReal causeFix
Startup error, or every request 404swire_api = "chat" (removed Feb 2026) or a gateway with no /responses endpointSet wire_api = "responses"
Every request returns 401env_key named but the variable never exportedExport the key in your shell profile
Model runs but returns the wrong outputCatalog slug does not match the provider's model IDMatch slug to the exact model string
Provider ignored, startup warning printedBlock lives in project-local .codex/config.tomlMove it to ~/.codex/config.toml
Intermittent connection resetsTrailing slash or wrong path on base_urlEnd the URL at /v1, no trailing slash

The tell that separates these from the real #19694 filter is one command: run the same request from the CLI. If the CLI also fails, it is one of the errors above, not the Desktop picker, and no catalog edit will fix it. If the CLI succeeds and only Desktop stays blind, then it is the filter — and you are back to Fix 1 and Fix 4.


ISSUE TIMELINE

Known Codex Desktop custom-model issues (2026)

This is not one bug. It is a cluster of related issues in the Codex app and in the tools that write its config. Knowing which one you have saves you from applying the wrong fix.

IssueWhat breaksReportedStatus
openai/codex #19694Desktop picker filters out catalog models the backend loaded2026-04-26Open
openai/codex #26308Desktop ignores project-local model_catalog_json on fresh threads2026Open
openai/codex #22160CLI /model and Desktop pickers did not expose profile / provider aliases2026Closed
openai/codex #15364No UI to pick a custom provider inside the Desktop app2026Closed
cc-switch #3668cc-switch catalog format not recognized, /model empty2026-06-03Fixed in v3.16.5

The pattern across all of these: the routing works, the display does not. The CLI has been the reliable escape hatch every time, because it reads the catalog without the Desktop renderer's extra filtering. If you are blocked in Desktop, the CLI (codex in your terminal) will almost always show and use the model.


WHEN THE PICKER STILL WON'T SHOW

Alternatives that work now

If you are done fighting the Desktop UI, here are ways to keep using custom models without waiting on a client fix.

PathHow the model reaches CodexPicker behaviorNotes
Codex CLISame config, terminal clientLists custom models reliablyBypasses the Desktop filter
A unified gatewayOne base_url, one key, many model IDs"Custom" or catalog-listedSwap models with a one-string edit
Direct provider keyA provider block per vendorSame filter appliesMore keys, more blocks to maintain
Local (Ollama)base_url at localhostWarnings unless catalog setOffline, no gateway

The point of a gateway here is one endpoint and one key across many models, so switching a model is a one-string edit rather than a new provider block each time. Whichever path you take, the Desktop picker quirk is a display layer on top, not a routing constraint.


VERIFY

How to confirm your models are actually loaded

Do not trust the picker as your source of truth — that is the thing that is broken. Verify at the layer below it.

codex --version                 # confirm you are on a recent build
codex                           # start the CLI, then type /model
# the CLI enumerates the catalog without the Desktop filter

If /model in the CLI shows your model, the catalog and provider are correct and any remaining gap is the Desktop renderer. If the CLI also comes up empty, the problem is upstream: wrong file (Fix 3), malformed catalog (Fix 2 / Fix 4), or a bad slug. For a raw sanity check that the route itself resolves, one curl against the gateway with your key tells you whether the model exists before you blame the client at all. Routing first, display second.


WHAT THIS ACTUALLY TEACHES

Your tooling's reliability shouldn't hinge on a UI filter

The fixes above get your picker back. The rest is the part worth acting on before the next bug.

You can read this bug two ways. The narrow way: a Desktop client filters out your models, set them inline, move on. The useful way: a tool you treated as part of your workflow silently dropped functionality at the rendering layer, and the only signal was that the dropdown looked empty.

We see this in nearly every AI system we audit. A team builds on one client, one UI, one default model, and a bug, a deprecation, or a quiet filter turns a working setup into a half-day of guessing. The Codex picker bug is just the most recent, most specific version of it.

Three things make a setup survive these changes:

Verify at the layer below the UI

Don't trust the picker. Trust the endpoint, the CLI, the curl. The display is the last thing to believe and the first thing to break.

Decouple the model from the tool

One config string swaps the model; one gateway swaps the provider. When a client filters or a vendor deprecates, your integration is a config change, not a rewrite.

Design for the bug, not the demo

Assume every client UI has a filter you can't see and every free path has an expiration date. Build the escape hatch before you need it.

If your workflow collapses when one client hides a model, the workflow failed the design test — not the client.


FAQ

Questions we get about this

Why is Codex Desktop not showing my custom models?+

Usually the model works and the Desktop app is filtering it out of the picker (issue #19694). Secondary causes: no catalog file, a malformed catalog from an older tool, or a provider defined in a project-local .codex/config.toml.

How do I add a custom model to Codex config.toml?+

Define [model_providers.NAME] with base_url, env_key, and wire_api, then set model_provider and model. Keep it in ~/.codex/config.toml. For a gateway, base_url ends at /v1 with no trailing slash.

Does cc-switch work with Codex Desktop?+

Yes, from v3.16.5, which generates ~/.codex/cc-switch-model-catalog.json for native Responses providers. Re-save each provider once after updating — the catalog only regenerates on save.

Where is the Codex model catalog file located?+

Wherever model_catalog_json points in ~/.codex/config.toml. cc-switch uses ~/.codex/cc-switch-model-catalog.json. Watch for a stale ~/.codex/models_cache.json after switching providers — delete it to force a clean rebuild.

Why does Codex show "Custom" instead of my model name?+

You set model inline with no catalog entry, so the picker has no display metadata. Requests still go to the right model. Add a model_catalog_json (Fix 2) to give it a real name.

Can I use custom models in a project-local .codex/config.toml?+

No. Provider settings only apply in ~/.codex/config.toml. Project-local provider blocks are ignored with a startup warning. Keep the provider user-level; keep repo-specific instructions in the project config.

What is model_catalog_json in Codex?+

A config.toml key pointing to a JSON file, loaded at startup, with a top-level models array of entries carrying slug, display_name, and capability fields. It overrides both the bundled and the remote catalogs.

What is wire_api and why does it matter?+

It selects the request protocol. Codex removed the chat/completions path in early February 2026; responses is now the only supported value and the default. A provider set to chat fails on startup, and a gateway with no /responses endpoint 404s every request.


About FACTA

FACTA helps startups and growth-stage teams turn AI into production systems that keep running — not demos that impress once.

We design the architecture around the parts that actually break under real usage: model abstraction, credentials you own, failover, cost controls, observability. The boring infrastructure that keeps a system alive after launch.

Led by Matías Baglieri and Carolina Fogliato, we focus on one thing:

AI leadership that builds. Not just advises.

Stop firefighting tooling bugs. Automate the layer.

When a client UI quietly drops your models, the fix is rarely one more config edit — it's an automation layer that keeps your tooling reliable, observable, and decoupled from any single client. FACTA designs and builds that layer so your workflow survives the next filter, deprecation, or breaking change.

Explore AI Automation
Book a 30-minute call →

No pitch. No pressure. Just a look at where your AI stack is fragile — and what to fix first.

Stay Updated

Get production AI insights in your inbox

Weekly insights. No spam. Unsubscribe anytime.