Repository navigation
(app-server and Desktop) thread/list treats omitted modelProviders as current-provider filter instead of all providers #24648
Description
What issue are you seeing?
The app-server thread/list API currently treats an omitted/null modelProviders field as an implicit filter for the currently configured model provider. If a third party app doesn't include the modelProviders field, then not all threads are shown. This issue is especially annoying when trying to continue a thread with a different model provider, as many apps, including Codex Desktop, don't see the thread with the new model provider. While this issue could be fixed both on the desktop and app server side, I think this is more of an app server issue than a desktop one, since an omitted filter shouldn't be filtering by default.
What steps can reproduce the bug?
App server
- Create local threads under at least two model providers, for example openai and lcpp.
- Configure Codex so the active provider is one of them, for example:
model_provider = "lcpp"
[model_providers.lcpp]
name = "Llama.cpp"
base_url = "http://localhost:8080/v1"
wire_api = "responses"- Call app-server thread/list with modelProviders omitted or set to null.
- Observe that only threads for the active provider are returned.
- Call thread/list again with:
{
"modelProviders": []
}
- Observe that threads from all providers are returned.
Desktop
- Open Codex with default OpenAI config
- Send message in a folder
- Quit Codex Desktop
- Change config to use another model and provider
- Reopen Codex
- See that threads previously used are missing, only threads that used that provider previously are visible.
This is also inconsistent with the CLI since the CLI still lists threads that were started with other model providers.
Here is a screenshot with my OpenAI login, using the default OpenAI provider and model=gpt-5.5 in the config:

After quitting Codex, changing my config to the following, this is what I see:
model_provider = "lcpp"
model = "qwen3.6-27b-mtp"
What is the expected behavior?
Current behavior appears to be:
- modelProviders: ["openai"] returns only openai threads.
- modelProviders: [] returns all providers.
- modelProviders: null / omitted returns only threads matching the current configured provider.
What it should be:
- modelProviders: ["lcpp"] means only lcpp.
- modelProviders: [] means all providers.
- modelProviders: null / omitted means also all providers.
On the desktop side, I expect that my threads will still show up after switching providers.
Additional information
I pointed Codex to the desktop app.asar, and it confirmed that the desktop app currently sends listAllThreads({ modelProviders: null }) which is why threads go missing in the desktop app after switching model providers. If the app server is behaving as intended and you would rather fix it on the desktop app side, then switch model providers to be modelProviders: [] and it will include all threads from all model providers. Again this is inconsistent with the CLI since in the CLI you can resume across different model providers. This issue is the root cause of
Relevant code is here in main:
| None => Some(vec![self.config.model_provider_id.clone()]), |
I also had drafted a fix but can't open a PR since I don't have permission: https://github.com/joshrad-dev/codex/tree/fix-thread-list-null-model-provider.
This code is the cause and fix for
the following issues:
After getting Codex to patch the ASAR to use [] instead of null for the modelProviders send, I'm now able to see all my threads across providers.

Activity
yui-stingray commentedon May 27, 2026
I checked current
origin/main, and the behavior described here matches the app-server boundary conversion.ThreadListFilters.model_providersis anOption<Vec<String>>. Inlist_threads_common, a non-empty list stays filtered,[]is converted to no provider filter, butnull/ omitted is converted toSome(vec![self.config.model_provider_id.clone()])before callingthread_store.list_threads(...). So the server is not merely passing the caller's omitted filter through; it is rewriting it into an active-provider filter.That narrows the fix to the contract decision: if the app-server protocol should treat omitted/null as all providers, the conversion in
list_threads_commonis the right boundary to change. If omitted/null is intended to mean current provider, then Desktop should send[]when it wants cross-provider history. A focused regression test should cover two stored threads with differentmodel_provider_idvalues and assert the chosen behavior for omitted/null,[], and a non-empty provider list.WAcry commentedon Jun 15, 2026
This also makes the current README misleading.
codex-rs/app-server/README.mddocumentsthread/list.modelProvidersas:“unset, null, or an empty array will include all providers.”
But current behavior appears to be:
So either the README should distinguish ordinary
thread/listfrom parentThreadId-filtered requests, or the app-server behavior should be changed to match the documented contract.rclevenger-hm commentedon Jun 27, 2026
I reproduced this on Codex Desktop for macOS while testing an OpenAI-compatible local proxy.
Observed behavior:
state_5.sqliterows and rollout JSONL files remain intact.The behavior appears consistent with the current conversion in
list_threads_common:modelProviders: []becomes no provider filter.modelProvidersbecomesSome(vec![self.config.model_provider_id.clone()]).Because Desktop omits this field when loading history, the app server implicitly filters the catalog to the currently configured provider.
A possible fix is to treat the optional provider filter as a filter only when it is non-empty:
That would establish these semantics:
modelProviders: all providersmodelProviders: []: all providersmodelProviders: only the requested providersSuggested regression coverage:
openaithread and oneother_providerthread.The protocol documentation should also state that omitted and empty provider filters include all providers.
j178 commentedon Jul 10, 2026
Additional macOS reproduction after upgrading from the standalone Codex app to the unified ChatGPT desktop app.
Environment:
openai-httpsopenaiAfter the upgrade/restart, the task list shows only sessions whose stored provider matches the currently configured
openai-httpsprovider.This matches the reported
thread/listbehavior: the desktop history request sendsmodelProviders: null, which App Server converts into a filter for the currently configured provider. PassingmodelProviders: []would return threads from all providers.The upgrade appears to have triggered a cold history reload; it did not delete or migrate the sessions.
Expected behavior: the default local history view should remain visible across providers, or Desktop should explicitly pass
modelProviders: []when loading the general task history.bbrzoska commentedon Jul 29, 2026
Fresh macOS reproduction confirming this remains present in the current unified ChatGPT desktop app.
Environment:
customObserved:
model_provideromitted/commented, the effective provider isopenai; the project shows no previous tasks.model_provider = "custom"makes the previous tasks appear immediately.~/.codex/state_5.sqlitecontains 72 rows for that project cwd, all withmodel_provider = "custom"; the rollout data is intact.Directly querying the bundled app-server with the same cwd reproduces the boundary behavior:
The installed desktop renderer still sends
modelProviders: nullfrom its recent-thread listing path. Currentmainalso still converts omitted/null providers toSome(vec![self.config.model_provider_id.clone()]), while an empty array removes the provider filter.This confirms the bug is not data loss or project-assignment corruption. It is provider-based history filtering, and it persists in the latest app build available to me. Desktop sending
modelProviders: []for general local history would resolve this case.Riki-Du commentedon Aug 4, 2026
issue still exists
Jensen-Liu76 commentedon Aug 11, 2026
I can confirm this issue still affects the current unified ChatGPT desktop
app on macOS.
Environment:
openaicustomandmodeldock_httpObserved behavior for one affected project containing 7 valid, unarchived
threads:
modelProviders: null-> 0 threadsmodelProvidersomitted -> 0 threadsmodelProviders: ["custom"]-> all 7 threadsmodelProviders: []-> all 7 threadsthread/readby ID succeeds for every affected threadThe corresponding rows remain present in
state_5.sqlite, and all rolloutJSONL files remain intact. Across the local database, 32 top-level CLI/VS
Code threads were hidden by the same provider filter.
As a reversible diagnostic confirmation, changing only the SQLite listing
metadata from the historical provider ID to
openaimade the threadsimmediately appear through the default
modelProviders: nullrequest. Theoriginal JSONL provider metadata remained unchanged and
thread/readcontinued to report the original provider. I do not recommend this database
workaround as the product fix, but it confirms that the problem is limited to
provider-based listing.
This matches the documented contract that unset, null, and an empty array
should include all providers.
A focused fix appears to be changing the app-server boundary so that only a
non-empty provider list applies a filter, with regression coverage for:
If the maintainers agree with this approach, I would be happy to implement
the fix and regression tests. Please let me know if you would like to invite
a PR.
hobbescalvin414-tech commentedon Aug 23, 2026
I prepared a focused fix for this issue, but the repository currently limits opening pull requests to collaborators.
Patch: hobbescalvin414-tech@d25e00c
Branch: https://github.com/hobbescalvin414-tech/codex/tree/fix/thread-list-null-model-providers
The change makes omitted/null and empty
modelProvidersdisable provider filtering, while preserving explicit filtering for non-empty arrays. It also extends the app-server test to cover omitted/null and empty-array behavior across multiple providers.Verification:
just fmtandgit diff --checkpassed. The app-server test suite was not completed because dependency downloading was stopped before compilation.A collaborator is welcome to cherry-pick commit
d25e00c88091e1beebd45e2998ee639a420e3e3a.claell commentedon Aug 23, 2026
Confirming this from working on a small local provider-manager tool for custom [model_providers.*] setups.
Omitted modelProviders behaving as a current-provider filter matches what we see in practice and what was reported in the closed #15494, plus #31625, #31627, and #17316. Sessions remain intact in rollouts and SQLite; only discovery narrows to the active provider bucket.
Defaulting local discovery to all providers with an explicit filter opt-in (as proposed here) would remove the main footgun of switching between the built-in OpenAI provider and custom ones. Happy to help validate once the default changes.
TheNewMikeMusic commentedon Sep 5, 2026
Additional reproduction on the official bundled CLI 0.153.4 / macOS Desktop 26.901.41600 (build 7982), with a specifically Android Remote pinned-section symptom.
The configured provider is an HTTP/SSE configuration named
openai_http, using the official OpenAI backend. Existing tasks include bothopenaiandopenai_httpmetadata. Five tasks are pinned: twoopenai_http, threeopenai.Observed on Android:
openai_httptasks appear in the pinned section.openaitask is still visible and accessible inside its project. This is a pinned-list visibility failure, not deletion of that task.A read-only control using the same real pinned section in the official app-server reproduces the exact set shown on the phone:
{ "method": "thread/list", "id": 1, "params": { "sectionId": "<pinned section ID from threadSection/list>", "limit": 100, "sourceKinds": [], "archived": false, "useStateDbOnly": true, "modelProviders": null } }modelProviders: nullopenai_httpmodelProviders: []The installed desktop's
listHostSectionThreadIdsimplementation already suppliesmodelProviders: [], explaining why its native pin list can be correct while a default-filtered caller loses three pins.We did not capture Android's raw list parameters, so this is an exact-result reproduction of the server behavior rather than proof of the mobile request payload. Please include pinned sections in the cross-provider client/server regression coverage, alongside recent and archived history.
These measurements were made with the official bundled executable. No native task database or rollout content was edited. Private titles, task IDs, host identifiers, account details, and raw logs are omitted. Related mobile report: #24780.
wyqmath commentedon Oct 5, 2026
+!
ShedlonA commentedon Oct 10, 2026
Additional Windows / Android Remote reproduction, checked on 10 October 2026.
A valid, non-archived chat is missing from the assistant-facing Desktop listing and from Android Remote. It is still present in the local desktop catalog, and
read_threadby ID succeeds.Environment:
26.1007.2314.0.package.json:26.1007.21434, build13901.codex-cli 0.162.0-alpha.17.2.custombelow. The missing chat is recorded underopenai.Read-only comparison of the local state database with
list_threads(limit=50)found 119 non-archived localvscoderows:openaiThe 19 ordinary local results exactly equal the current-provider rows minus their pins. There were no additional exclusions in that group. Of the 97
openairows, 95 were absent from the entire captured result.These counts describe the assistant's listing tool, not the native desktop sidebar. The complete tool result contained 50 non-pinned records across local Codex, remote Codex and ChatGPT, plus 7 pins; it reported no unavailable hosts or sources.
Inspection of the installed assistant wrapper shows that its
thread/listrequest includesmodelProviders: null,cursor: nullanduseStateDbOnly: true. This matches the provider-filter conversion in the current public source: for an ordinary listing, null/omitted selects the configured provider, while an empty array removes the provider filter. We have not established that the installed binary was built from that exact commit.On Android, after selecting the Windows host, refreshing and searching, the older
openaichat remains missing. A current-provider control chat does appear. The missing chat uses an ordinary project folder, not a managed worktree.Android limitation: we have not captured the phone's actual request parameters or response. The two phone checks are consistent with provider filtering, but do not prove Android uses the same request path. We also have not run a live null-versus-empty-array API comparison on this host.
The separate 50-result limit in #41249 is also present in the assistant wrapper: it discards the backend continuation cursor. That is an additional discovery limitation; increasing the limit alone would not address the provider filter.
Please include general history discovery and Android Remote in the cross-provider regression coverage. A client intending to list all providers should request
modelProviders: []under the current server behavior, with pagination retained.No configuration, application bundle or history metadata was changed during this investigation. Private chat titles, IDs, local paths and provider endpoints are omitted.
nostitos commentedon Oct 14, 2026
Another reproduction on Codex Desktop 26.1007.30912, app-server 0.162.0-alpha.17.2 (macOS host, ChatGPT for Android Remote). For one project,
thread/listreturns 2 threads withmodelProvidersomitted and 46 with[], and the Android list shows the same 2. On this host the Android client also saves the threads it starts underopenaiinstead of the custom default, so threads started from the phone vanish from the phone once they go idle. Write-up: #53848.