Repository navigation
Desktop custom providers are unusable with existing chats and the model picker #29156
Description
Related to #10867, but this is a more specific Desktop failure mode around custom providers, the model catalog, and existing chat history.
Summary
Codex CLI/TUI can use custom model providers reasonably well via model_providers and profiles. Codex Desktop cannot do the same in a sane, safe way.
The current Desktop behavior makes third-party providers such as OpenRouter/LiteLLM/internal OpenAI-compatible gateways effectively unusable if the user wants all three of these at once:
- use the Desktop GUI
- select custom-provider models from the GUI model picker
- keep access to existing Desktop chats/history
I spent time trying to make Codex Desktop use OpenRouter models while preserving normal chats. Every available path either fails outright, hides/segregates chats, replaces the built-in catalog, or requires temporary mutation of ~/.codex/config.toml.
Environment
- macOS arm64
- Codex CLI/App Server:
0.142.0-alpha.1 - Codex Desktop:
26.616.32156 - Provider tested: OpenRouter, Responses API-compatible
- Example models tested:
moonshotai/kimi-k2.7-codedeepseek/deepseek-v4-proqwen/qwen3-coder-plusz-ai/glm-5.2minimax/minimax-m3
What works
CLI/TUI usage works with custom provider config, for example:
[model_providers.openrouter]
name = "OpenRouter"
base_url = "https://openrouter.ai/api/v1"
env_key = "OPENROUTER_API_KEY"
wire_api = "responses"
supports_websockets = false
[profiles.deepseek]
model_provider = "openrouter"
model = "deepseek/deepseek-v4-pro"
model_reasoning_effort = "high"Then:
codex --profile deepseekThat path is understandable and relatively safe.
What fails in Desktop
1. Desktop has no supported provider-aware model picker
Generated app-server protocol/schema inspection shows model/list only accepts:
cursorlimitincludeHidden
The Model response does not include provider identity. The Desktop frontend calls list-models-for-host with includeHidden: true, but there is no provider-aware external model list surfaced to the picker.
Desktop model changing appears to update model/reasoning, not provider.
2. Provider support exists at thread creation, but not as a normal GUI switch
The app-server protocol has ThreadStartParams.modelProvider, which suggests provider-aware threads are supported at creation time.
However, TurnStartParams can override model but not modelProvider, so the GUI has no obvious way to switch provider per turn/thread from the existing composer flow.
This makes custom-provider usage feel half-wired: the backend/config understand providers, but the Desktop UX does not expose a coherent model/provider switcher.
3. model_catalog_json is replacement-only and dangerous for Desktop UX
model_catalog_json looks like it should solve this, but it replaces the bundled catalog for the current process. It does not append or merge custom models into the normal catalog.
Public source comments also describe it as a replacement mechanism. In practice, using it to add custom OpenRouter models removes the normal Codex/OpenAI model catalog from that process.
This is dangerous because it looks like an extension point but behaves like a full override. A user trying to add one provider/model can accidentally make the normal Desktop model picker/catalog behavior disappear.
4. Saved custom models fall back if absent from the active catalog
Desktop model settings code falls back when the saved model is not present in list-models data. So simply putting a custom model ID in config is not enough to make Desktop behave like that custom model is a normal selectable GUI model.
5. Separate CODEX_HOME avoids damaging the main config but loses normal chats
Launching Desktop with a separate CODEX_HOME can load a custom provider/catalog safely, but then the GUI does not show the user’s normal chats because chats/state are tied to the active Codex home and Electron app state.
Trying to symlink the main state/session files into the alternate home is not reliable enough: the GUI can show folders/projects but still not show the actual chats as expected. That implies there is more Desktop UI state/provider filtering involved than just the raw state DB files.
6. Using the real ~/.codex profile requires unsafe config mutation
The only path that seems likely to preserve all chats is launching the real Desktop profile (~/.codex) after temporarily editing ~/.codex/config.toml to set:
model_provider = "openrouter"model = "..."model_catalog_json = "..."[model_providers.openrouter] ...
Then restoring the config after Desktop quits.
That is not a sane supported workflow. It is fragile, risks leaving the main config in a broken state if the launcher crashes, and is exactly the kind of workaround that can make users think their chats/models disappeared.
7. Fake-auth workarounds are not a real solution
The requires_openai_auth = true workaround mentioned in #10867 can make parts of the picker render, but it is conceptually wrong: it requires fake OpenAI auth and still does not provide a proper multi-provider/multi-profile Desktop switcher.
It also does not solve the underlying issue that Desktop treats OpenAI/ChatGPT auth/catalog state as the gate for model switching rather than treating configured providers as first-class model sources.
Expected behavior
Codex Desktop should support custom providers as first-class GUI targets:
- The model picker should be provider-aware.
- Custom provider models should be discoverable from
/v1/modelswhere possible, or from explicit configured model IDs. - There should be a safe append/merge mechanism for custom model catalog entries.
model_catalog_jsonshould either be renamed/documented as full replacement, or a separate additiveextra_model_catalog_json/model_catalogsmechanism should exist.- Switching provider/model should not make existing chats appear gone.
- If threads are intentionally provider-scoped, the UI should make that explicit and provide an “all providers/all chats” view.
- Users should not need fake OpenAI auth, separate
CODEX_HOME, symlinks, or temporary config rewrites.
Why this feels intentional/adversarial from the user side
I am not claiming to know maintainer intent. But the current behavior creates a reasonable perception of intentional lock-in or bad faith because the failure is not a single missing UI checkbox.
Several independent pieces all push in the same direction:
- Custom providers work in CLI config, proving the core abstraction exists.
- Desktop does not expose those providers as first-class picker options.
- The app-server protocol supports
modelProviderat thread start, but the GUI does not provide a normal provider switcher. model_catalog_jsonlooks like a customization hook, but actually replaces the full bundled catalog.- Changing provider/profile can make existing chats appear missing or segregated.
- Workarounds require fake OpenAI auth, alternate homes, symlinks, restarts, or risky config mutation.
- The product does not clearly warn users that these paths can hide normal Desktop model/chat behavior.
From a user perspective, this is indistinguishable from a deliberate product boundary: custom providers are supported just enough for CLI/power users, but Desktop remains effectively tied to the built-in OpenAI/ChatGPT catalog and auth flow.
If this is not intentional, the Desktop app needs a clear supported path for custom providers. If it is intentional, the limitation should be documented plainly instead of leaving users to discover it by breaking their Desktop model picker or thinking their chats disappeared.
Request
Please clarify the intended support level for custom model providers in Codex Desktop, and ideally implement first-class Desktop support for:
- provider selection
- custom-provider model discovery
- additive custom model catalog entries
- preserving/seeing existing chats across provider changes
At minimum, please document that model_catalog_json is a full catalog replacement and not a safe way to add models to Desktop.
Activity
github-actions commentedon Jun 20, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
bradleygibsongit commentedon Jun 21, 2026
+1
clavelinaswykoniki-cell commentedon Jun 24, 2026
Adding another user-impact case for this issue, focused on Desktop custom-provider support for common China-region model providers.
Environment:
codex-cli 0.142.0User expectation:
With the recent "native" custom-provider support, users expect Codex Desktop to make it possible to add and use common third-party providers from the GUI, including providers/models such as DeepSeek, Moonshot/Kimi, Qwen/DashScope, Zhipu/GLM, MiniMax, Doubao/Volcengine, Baidu/ERNIE, Tencent/Hunyuan, or compatible gateways such as OpenRouter/LiteLLM.
Observed product problem:
I am not claiming all providers listed above have been fully reproduced in this exact environment; this is a user-impact report and compatibility/documentation request. The current behavior makes Desktop custom-provider support hard to evaluate and easy to misconfigure.
Suggested improvements:
/v1/responsessemantics vs/chat/completions.This matches the broader issue here: custom providers appear partly supported by config and app-server primitives, but Desktop does not expose a coherent, safe path for everyday use.
brotherhc commentedon Jul 29, 2026
This remains a significant product limitation in the current macOS desktop app (
26.721.41059, build5848).The official documentation says the desktop app, CLI, and IDE extension share
~/.codex/config.toml, and the CLI supports custom model providers. In practice, however, Desktop still does not offer a provider-aware model picker or a reasonable way to switch between configured third-party models.For an ordinary Desktop user, “support” currently means manually editing global configuration, managing credentials outside the app, restarting the app, and starting a new task. That is not credible first-class support, especially when the CLI already has profiles and explicit provider/model controls.
The UX is particularly frustrating because:
Please either make configured providers first-class entries in the Desktop model picker, with provider/model selection and connection validation, or document the Desktop limitation plainly. The current half-supported state is confusing and feels unnecessarily restrictive.
A new Desktop build (
26.721.81911, build5973) was offered on July 29, but its appcast contains no release notes, so users also cannot determine whether this limitation has changed without installing and retesting it.hasani114 commentedon Aug 1, 2026
We need this feature asap. Its annoying to have to go through so many hoops to use other models in the Desktop App,
nostitos commentedon Aug 2, 2026
rimanov2000 commentedon Aug 5, 2026
Reproduced on Windows 11, Codex Desktop 26.730.8199.
With a ChatGPT-authenticated parent task on GPT-5.6 Terra and a valid custom DeepSeek provider configuration, selecting
deepseek-v4-flashin the Desktop model picker reports “Model changed…” and then fails:The 'deepseek-v4-flash' model is not supported when using Codex with a ChatGPT account.The custom provider itself is functional: a named custom subagent with
model_provider = "deepseek"andmodel = "deepseek-v4-flash"successfully ran while the GPT parent remained untouched. This appears to be a Desktop-picker routing bug: it changes the model name without atomically selecting the configured provider.Please make the picker provider-aware, or hide incompatible custom-provider model IDs until the provider can be selected.
rimanov2000 commentedon Aug 5, 2026
sorry for slop, but regardless what i've tried it did not work.
claell commentedon Aug 23, 2026
Supporting this request. The combination of a non-provider-aware model picker, provider-bucket session filtering (#24648), and full-catalog replacement semantics for model_catalog_json makes custom providers hard to use safely on Desktop today.
Even a documented support matrix (which surfaces are custom-provider aware, and whether model_catalog_json replaces or merges) would help third-party tools set correct expectations.
Miensoap commentedon Aug 29, 2026
Root cause: the picker swaps the model slug but never rebinds the thread's persisted provider
Reproduced and root-caused this on Codex App 26.825.32147 (bundled codex-cli 0.150.0-alpha.12.2; same behavior driving
codex app-serverfrom codex-cli 0.150.1), macOS arm64, signed in with a ChatGPT account, custom Responses-compatible provider configured throughmodel_providers+model_catalog_json.Measured failure. On an existing thread whose persisted binding is the built-in OpenAI provider, selecting a custom-provider model in the thread's model picker and sending a message fails the turn with the raw backend response recorded in
thread_turns.error_json:{"type":"error","status":400,"error":{"type":"invalid_request_error", "message":"The '<custom-model>' model is not supported when using Codex with a ChatGPT account."}}The request never reaches the custom provider - it is sent to the ChatGPT-account backend because the thread is still bound to the built-in OpenAI provider.
Where the binding lives. The per-thread provider is persisted in the engine thread registry (
~/.codex/state_*.sqlite, tablethreads:model,model_provider,reasoning_effort) and is whatthread/resumereturns asmodel/modelProvider(verified with a plainthread/resumethroughcodex app-server: the response follows the registry row immediately, in both directions). New threads pick up the config-default provider; resumed threads keep their persisted one.Diagnosis. The model picker updates only the model selection for the thread; it does not rebind the persisted provider. So a custom-provider model selected on an OpenAI-bound thread is routed to the ChatGPT backend and rejected. Out of spec, but to confirm the routing side: updating that registry row to the custom provider makes subsequent resumes/turns use the custom provider correctly - the routing information exists, the picker just never writes it.
Suggested directions.
model_catalog_json/model_providers), rebind the thread's provider together with the model; orHappy to provide full session ids / exact configs if useful.
Razkar1 commentedon Sep 3, 2026
Confirmed on the current macOS Desktop build
26.901.20858(build7658) with a custom Responses-compatible provider.I was able to get a custom model and the normal ChatGPT/Codex models to coexist in the native Desktop model selector. The remaining failure was provider binding: selecting the custom model for a new thread still produced:
The useful finding is that the backend path already supports the desired behavior. On build 7658 I patched only the Desktop request layer so that, when a new thread starts, the selected model is accompanied by the correct
ThreadStartParams.modelProvider. I also applied the same routing to the prewarmed-thread path. No globalmodel_providerwas set, and the normal ChatGPT login remained active.With that one change, the behavior became:
This worked live:
muse-spark-1.3-contributorwas routed through a localmeta_model_apiprovider while GPT-5.6 Sol/Terra/Luna and GPT-5.3 Codex Spark continued using the signed-in ChatGPT/OpenAI provider. The custom and built-in models remained visible in the same native selector.That strongly suggests the missing Desktop feature for new threads is relatively narrow: the picker/model-selection layer needs to preserve or resolve provider identity and send
modelProvidertogether withmodelonthread/start(including the prewarm path). The app-server already accepts it and routes correctly.This does not solve mid-thread provider switching; as noted elsewhere in this issue, existing threads have a persisted provider binding and would need explicit rebinding or a new thread.
I published the build-7658 proof-of-concept here:
Keksuccino/Better-Codex-App-Custom-Provider-Support#3
The patch intentionally leaves the native model picker unchanged and only injects per-model provider routing at thread creation. It also fails closed on source-layout changes. I hope this helps narrow a native implementation: provider-aware model entries +
modelProvideron thread creation appear sufficient for the new-thread case.fix(desktop-apps): Configure writes values the apps accept (6.67.0)
fix(desktop-apps): Configure writes values the apps accept (6.67.0) (#41
waeljammal commentedon Sep 16, 2026
Adding my 2 cents here as well, we really need this too. We would really like to be able to switch between our private models and gpt models.
sudeekshamurari commentedon Sep 23, 2026
Additional reproduction and app-server interface detail for the existing-thread case. Provider/model identifiers below are placeholders.
Reproduction
Environment: Codex Desktop on macOS, signed in with a ChatGPT account, with a custom Responses-compatible provider configured through
model_providersand models exposed throughmodel_catalog_json.The turn fails with:
The selected model is not routed to the custom endpoint. A fresh task can work when the custom provider is resolved at thread creation, so this is not a blanket failure of the custom Responses endpoint.
Interface gap
In the app-server source/schema inspected:
Modelentries returned bymodel/listhave model IDs and reasoning capabilities, but no provider identity (codex-rs/app-server-protocol/src/protocol/v2/model.rs).ThreadSettingsUpdateParamsacceptsmodelandeffort, but nomodelProvider. In contrast, the resultingThreadSettingsincludesmodel_provider(codex-rs/app-server-protocol/src/protocol/v2/thread.rs).merge_persisted_resume_metadatarestores the persisted model/provider when there is no explicit model/provider resume override (codex-rs/app-server/src/request_processors/thread_processor.rs).This is consistent with the earlier reports: changing a model slug through the picker/settings path does not provide an atomic way to change the thread's provider binding.
Expected behavior
Selecting a configured model should resolve and apply its provider together with the model, preserving the current conversation. The same operation should validate the reasoning effort against the selected model's supported values and use its default if the previous value is incompatible.
A possible implementation is provider identity in model entries plus provider rebinding in
thread/settings/update. New-thread and prewarm paths should likewise carry the selected provider with the model. Users should be able to use the normal picker without editing session databases, restarting the app, or abandoning an existing task.