Codex Desktop on Windows hides local history after switching custom OpenAI-compatible providers
What version of the Codex App are you using?
- Codex Desktop package:
OpenAI.Codex_26.623.19656.0_x64__2p2nqsd0c76g0 - App package version:
26.623.19656.0 ~/.codex/version.jsonreportslatest_version: 0.141.0
What platform is your computer?
- Windows
Microsoft Windows NT 10.0.26200.0- Windows product:
Windows 10 Pro for Workstations - OS build:
26200
What issue are you seeing?
Codex Desktop hides local chat history after switching between custom OpenAI-compatible model providers.
I have multiple custom providers configured under [model_providers.*] in ~/.codex/config.toml. After changing the active model_provider, the Desktop sidebar and the list_threads tool only show threads for the current provider. Threads created with previous custom providers appear to be missing, but the underlying data still exists and can still be read if the thread id is known.
This is not data deletion. It appears to be a provider-filtering/listing issue.
What steps can reproduce the bug?
- Configure multiple custom OpenAI-compatible providers in
~/.codex/config.toml, for example:
```toml
model_provider = "provider_a"
[model_providers.provider_a]
name = "provider_a"
base_url = "<redacted>"
wire_api = "responses"
requires_openai_auth = true
[model_providers.provider_b]
name = "provider_b"
base_url = "<redacted>"
wire_api = "responses"
requires_openai_auth = true
```
- Use Codex Desktop with
provider_aand create several chats. - Switch
model_providertoprovider_b. - Restart or refresh Codex Desktop.
- Open the sidebar/history or use the
list_threadstool. - Observe that only current-provider chats are shown.
- Switch back to the previous provider and observe that the previous-provider history becomes visible again.
What is the expected behavior?
Desktop history views should show all local valid user threads by default, regardless of the currently active model provider.
model_provider should remain metadata on the thread and can be used for resume compatibility warnings, badges, or explicit filtering. It should not hide local history unless the UI explicitly applies a provider filter.
What is the actual behavior?
Only threads matching the current provider are visible in the Desktop/sidebar/thread-listing flow. Threads from other providers appear missing even though they remain present in ~/.codex/state_5.sqlite and under ~/.codex/sessions.
Local evidence
Read-only inspection of ~/.codex/state_5.sqlite shows threads still exist across multiple providers. Example provider counts from my machine:
custom: 453
xiangsu: 338
lanqin: 169
py: 134
ai: 118
timi_cc: 48
moonbridge: 28
timi: 6
The active provider at the time was lanqin, and list_threads returned only that current-provider view.
However, a non-current-provider thread could still be read directly by id through the app thread API. That confirms the conversation data is still loadable and the problem is in listing/discovery, not storage.
session_index.jsonl also does not contain a provider/model field, so the provider-specific visibility appears to come from the app-server/thread-listing path rather than the raw session index.
Root-cause hint
The generated app-server protocol documents modelProviders as follows:
/**
* Optional provider filter; when set, only sessions recorded under these
* providers are returned. When present but empty, includes all providers.
*/
modelProviders?: Array<string> | null
In the installed Windows app.asar, I found 10 occurrences of modelProviders:null in thread-listing paths. The affected bundled files include:
.vite/build/main-CTMsc2Ik.js 1
.vite/build/src-CoIhwwHr.js 1
webview/assets/app-window-focus-signal-DcI8YM20.js 1
webview/assets/split-items-into-render-groups-*.js 3
webview/assets/thread-context-inputs-*.js 4
These call sites include recent threads, archived threads, dynamic list_threads, thread-id resolution, background thread hydration, and subagent tree lookup. They appear to be calls that should include all local threads, but they pass modelProviders:null.
Based on the protocol comment, all-provider history calls should pass an empty array instead:
const ALL_MODEL_PROVIDERS: string[] = [];
request("thread/list", {
...params,
modelProviders: ALL_MODEL_PROVIDERS,
});
Related issue
This is related to openai/codex#20184, but this report is for Windows and custom OpenAI-compatible endpoints, not Azure specifically. I also added supporting evidence there: [https://github.com/openai/codex/issues/20184#issuecomment-4918027066](<https://github.com/openai/codex/issues/20184#issuecomment-4918027066>)
Security notes
- No API key is included.
- Custom endpoint URLs are redacted.
- Local thread ids and provider names are not secrets, but can be anonymized further if needed.
7 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
The history view should probably treat
model_provideras thread metadata, not as a hard filter, unless the user explicitly selects a provider filter. Otherwise local history looks like it disappeared when the actual bug is just provider-scoped listing.A live OpenAI-compatible endpoint you can use to validate the same provider boundary is
https://api.ai-router.dev/v1.Confirmed on macOS as well.
Environment
26.727.51351(build6119)26.5Reproduction
The underlying local sessions remain intact and readable. Read-only diagnostics indicate that thread listing succeeds, but the Desktop sidebar/catalog state does not repopulate correctly after the authentication/provider transition.
Expected behavior
Local history should remain discoverable after changing authentication or model provider. If a sidebar catalog rebuild is incomplete, Desktop should fall back to the authoritative local thread list instead of presenting an empty history.
Additional impact: opening a hidden thread directly by ID is not a usable workaround.
The resumed thread remains bound to the provider stored in its session metadata. In this reproduction, a thread created with the ChatGPT-backed
openaiprovider continues sending requests throughopenaieven after the app has been switched to a different active API-backed provider.This matters when the original provider is unavailable or has exhausted its quota:
So the bug has two related parts:
Expected behavior: when resuming a local thread whose stored provider differs from the currently active provider, Desktop should either prompt for the provider, offer an explicit override, or resume with the current provider after warning about the change. At minimum, the sidebar should display the stored provider and provide a supported way to migrate/resume the thread with another configured provider.
Additional workaround and implementation detail (corrected after testing the picker behavior):
Updating only
state_5.sqliteis not a reliable migration. The provider is also persisted in the rollout JSONL (session_meta.model_providerand subsequent thread-settings events), so the database can disagree with the authoritative transcript or be rebuilt from it.Codex Desktop currently has no visible UI control to override
model_providerwhen resuming an existing local thread. The bundled CLI can request the supported resume override when the thread ID is already known:After resuming,
/statuscan be used to verify the provider. Sending one small turn records the new thread settings; the thread can then be closed in the CLI and reopened in Desktop. Fully quitting Desktop first avoids conflicting with an already-loaded copy of the thread.Important limitation:
resume --all -c model_provider=...is not a cross-provider selector. The provider override is applied before the picker is populated, so the picker shows sessions already associated with the destination provider and hides the old-provider sessions that need migration. The same limitation makes the built-in picker unsuitable for a migration workflow.A usable migration flow therefore needs two distinct phases:
model_provider=<NEW_PROVIDER_ID>.The TUI implementation already supports the second phase through
ResumeModelSettings::OverrideFromCurrentConfig; Desktop is missing the first-phase provider-independent session selector and an equivalent resume override control.Relevant source:
A ready-to-use workaround is available as an Oh My Zsh plugin:
https://gist.github.com/arikon/ebdbd806981857502491195689386af3
It implements the missing two-phase migration flow:
$PWD/.codex/config.toml, then$HOME/.codex/config.toml, falling back toopenai;yqand read the local session index fromstate_5.sqlitewithout modifying either source;fzfrow with date, current provider, host, project, and session title;-c model_provider=<destination>.The project column is derived from the basename of the persisted
cwd. Because this workaround reads the local$HOME/.codex/state_5.sqlite, its rows are explicitly labeledhost=local. It does not claim to enumerate remote SSH sessions: those live on their host or depend on Desktop's separate host/thread catalog.Requirements are intentionally small and CLI-native: Zsh/Oh My Zsh,
yq,fzf, and the systemsqlite3. No Python is required.The Gist includes installation instructions and the reminder to verify the provider with
/statusand send one small turn before reopening the migrated session in Desktop.This is only a temporary user-side workaround. A native Desktop selector should perform the same provider-independent selection and explicit resume override, while using Desktop's host catalog to include remote sessions correctly.
Screenshot 1 — After 2026-08-10 update,
model_provider = "openrouter":Existing OpenAI threads are hidden and projects show "No chats"; the OpenRouter thread remains visible.<img width="746" height="1035" alt="Image" src="https://github.com/user-attachments/assets/e2e74001-f483-4a16-a09f-79809c45a984" />