(app-server and Desktop) thread/list treats omitted modelProviders as current-provider filter instead of all providers

Open 💬 9 comments Opened May 26, 2026 by joshrad-dev

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

  1. Create local threads under at least two model providers, for example openai and lcpp.
  2. 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"
  1. Call app-server thread/list with modelProviders omitted or set to null.
  2. Observe that only threads for the active provider are returned.
  3. Call thread/list again with:
  {
    "modelProviders": []
  }
  1. Observe that threads from all providers are returned.

Desktop

  1. Open Codex with default OpenAI config
  2. Send message in a folder
  3. Quit Codex Desktop
  4. Change config to use another model and provider
  5. Reopen Codex
  6. 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:
<img width="449" height="352" alt="Image" src="https://github.com/user-attachments/assets/e93d1f14-3a51-45a0-b2d2-851ac21a71fd" />

After quitting Codex, changing my config to the following, this is what I see:

model_provider = "lcpp"
model = "qwen3.6-27b-mtp"

<img width="433" height="500" alt="Image" src="https://github.com/user-attachments/assets/71e9a06e-df12-4ecd-bdc7-3f4aec71db40" />

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: https://github.com/openai/codex/blob/3da89d4831251b704ac40d1357d26ad518abaf1d/codex-rs/app-server/src/request_processors/thread_processor.rs#L3457

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:

https://github.com/openai/codex/issues/20184
https://github.com/openai/codex/issues/20004
https://github.com/openai/codex/issues/19088

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.
<img width="709" height="589" alt="Image" src="https://github.com/user-attachments/assets/098b82fd-9b07-44b0-8500-280ce29872bf" />

View original on GitHub ↗

9 Comments

yui-stingray · 3 months ago

I checked current origin/main, and the behavior described here matches the app-server boundary conversion.

ThreadListFilters.model_providers is an Option<Vec<String>>. In list_threads_common, a non-empty list stays filtered, [] is converted to no provider filter, but null / omitted is converted to Some(vec![self.config.model_provider_id.clone()]) before calling thread_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_common is 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 different model_provider_id values and assert the chosen behavior for omitted/null, [], and a non-empty provider list.

WAcry · 2 months ago

This also makes the current README misleading.

codex-rs/app-server/README.md documents thread/list.modelProviders as:
“unset, null, or an empty array will include all providers.”

But current behavior appears to be:

  • omitted / null: current provider, except parentThreadId-filtered requests
  • []: all providers

So either the README should distinguish ordinary thread/list from parentThreadId-filtered requests, or the app-server behavior should be changed to match the documented contract.

rclevenger-hm · 2 months ago

I reproduced this on Codex Desktop for macOS while testing an OpenAI-compatible local proxy.

Observed behavior:

  1. Existing chats briefly appear when Codex Desktop starts.
  2. The chats disappear after the app-server thread list response arrives.
  3. The corresponding state_5.sqlite rows and rollout JSONL files remain intact.
  4. Restoring the original routing configuration makes the catalog visible again.

The behavior appears consistent with the current conversion in list_threads_common:

  • modelProviders: [] becomes no provider filter.
  • An omitted/null modelProviders becomes Some(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:

let model_provider_filter = model_providers
    .and_then(|providers| (!providers.is_empty()).then_some(providers));

That would establish these semantics:

  • omitted/null modelProviders: all providers
  • modelProviders: []: all providers
  • non-empty modelProviders: only the requested providers

Suggested regression coverage:

  • Create one openai thread and one other_provider thread.
  • Verify omitted and empty filters return both threads.
  • Preserve the existing test proving a non-empty filter returns only the requested provider.

The protocol documentation should also state that omitted and empty provider filters include all providers.

j178 · 1 month ago

Additional macOS reproduction after upgrading from the standalone Codex app to the unified ChatGPT desktop app.

Environment:

  • ChatGPT desktop: 26.707.31428
  • Bundled Codex CLI: 0.144.0-alpha.4
  • macOS arm64
  • Current provider: custom openai-https
  • Previous sessions: built-in openai

After the upgrade/restart, the task list shows only sessions whose stored provider matches the currently configured openai-https provider.

This matches the reported thread/list behavior: the desktop history request sends modelProviders: null, which App Server converts into a filter for the currently configured provider. Passing modelProviders: [] 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 · 1 month ago

Fresh macOS reproduction confirming this remains present in the current unified ChatGPT desktop app.

Environment:

  • ChatGPT desktop: 26.721.41059
  • Bundled Codex CLI/app-server: 0.146.0-alpha.3.1
  • macOS 26.5.2, arm64
  • Signed in with ChatGPT; custom OpenAI-compatible provider ID: custom

Observed:

  • With model_provider omitted/commented, the effective provider is openai; the project shows no previous tasks.
  • Setting model_provider = "custom" makes the previous tasks appear immediately.
  • ~/.codex/state_5.sqlite contains 72 rows for that project cwd, all with model_provider = "custom"; the rollout data is intact.

Directly querying the bundled app-server with the same cwd reproduces the boundary behavior:

modelProviders: null  -> data: []
modelProviders: []    -> the persisted Zendesk tasks are returned

The installed desktop renderer still sends modelProviders: null from its recent-thread listing path. Current main also still converts omitted/null providers to Some(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 · 24 days ago

issue still exists

Jensen-Liu76 · 16 days ago

I can confirm this issue still affects the current unified ChatGPT desktop
app on macOS.

Environment:

  • ChatGPT desktop: 26.803.61601, build 6396
  • Bundled Codex app-server: 0.147.0-alpha.6.5
  • macOS 26.3, arm64
  • Current default provider: openai
  • Historical threads: custom and modeldock_http

Observed behavior for one affected project containing 7 valid, unarchived
threads:

  • modelProviders: null -> 0 threads
  • modelProviders omitted -> 0 threads
  • modelProviders: ["custom"] -> all 7 threads
  • modelProviders: [] -> all 7 threads
  • thread/read by ID succeeds for every affected thread

The corresponding rows remain present in state_5.sqlite, and all rollout
JSONL 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 openai made the threads
immediately appear through the default modelProviders: null request. The
original JSONL provider metadata remained unchanged and thread/read
continued 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:

  1. omitted/null -> all providers
  2. empty array -> all providers
  3. non-empty array -> requested providers only

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 · 5 days ago

I prepared a focused fix for this issue, but the repository currently limits opening pull requests to collaborators.

Patch: https://github.com/hobbescalvin414-tech/codex/commit/d25e00c88091e1beebd45e2998ee639a420e3e3a
Branch: https://github.com/hobbescalvin414-tech/codex/tree/fix/thread-list-null-model-providers

The change makes omitted/null and empty modelProviders disable 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 fmt and git diff --check passed. 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 · 5 days ago

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.