Summary
When multiple providers expose the same model ID/name in the conversation model picker, the picker loses provider identity.
In my current local setup, /api/models returns a duplicate model ID:
custom:edith -> gpt-5.4
openai-codex -> gpt-5.4
This causes two visible problems in the chat/session model picker:
- all duplicate rows render as selected/active at the same time
- clicking another provider's copy of the same model is treated as a no-op, so the effective provider never switches
In practice it looks like the UI always keeps using the first matching provider entry.
Reproduction
- Configure multiple providers that expose the same upstream model name / ID
- example from my local setup:
custom:edith and openai-codex both expose gpt-5.4
- Open the conversation model dropdown in the session footer
- Select the
gpt-5.4 entry under one provider
- Re-open the dropdown and click the
gpt-5.4 entry under the other provider
Actual behavior
- both duplicate rows appear active/selected
- clicking the other provider's duplicate does not really switch provider identity
- the persisted session model remains the same raw string value, so the request path keeps using whichever provider mapping wins first
Expected behavior
Each provider/model entry should be uniquely selectable, even when multiple providers expose the same upstream model string.
Root cause from source inspection
This looks like a provider-identity bug caused by using the raw model string as the only option identity.
1. /api/models can emit duplicate IDs across providers
In api/config.py, _apply_provider_prefix() skips prefixing when active_provider is empty:
# api/config.py
_active = (active_provider or "").lower()
if not _active or provider_id == _active:
return list(raw_models)
So if active_provider is None, multiple providers can emit the same bare ID unchanged.
2. Named custom provider groups also bypass provider-unique encoding
In the named custom-provider group path, models are appended as raw IDs and then added directly to groups:
# api/config.py
_named_custom_groups[_slug][1].append({"id": _cp_model, "label": _cp_label})
...
groups.append({"provider": _nc_display, "provider_id": pid, "models": _nc_models})
That means a named custom provider like custom:edith can contribute bare gpt-5.4, which then collides with another provider group that also exposes bare gpt-5.4.
3. The frontend selection state is keyed only by value
In static/ui.js, the custom dropdown marks rows active only by comparing m.value === sel.value:
row.className = 'model-opt' + (m.value === sel.value ? ' active' : '');
row.onclick = () => selectModelFromDropdown(m.value);
So once two rows share the same value, both rows render active.
4. Clicking a duplicate value is treated as "no change"
selectModelFromDropdown() returns early when the current <select> already has the same raw value:
if(!sel || sel.value === value) { closeModelDropdown(); return; }
So clicking the other provider's copy of the same value does not switch anything.
5. Session persistence only stores the raw model string
static/boot.js sends only model:selectedModel to /api/session/update:
const selectedModel = $('modelSelect').value;
await api('/api/session/update', {
method: 'POST',
body: JSON.stringify({
session_id: S.session.session_id,
workspace: S.session.workspace,
model: selectedModel,
})
});
So once provider identity is lost in the dropdown value, it is also lost in session persistence / backend resolution.
Why this seems distinct from earlier related issues
This looks related to earlier provider-routing / dedup work such as #138 and #907, but the current bug is specifically about duplicate option values across providers in the session model picker, which breaks:
- visual active state
- click-to-switch behavior
- persisted provider identity
Suggested fix direction
I think the safest fix is: ensure every provider/model entry in the picker has a globally unique value.
Concretely:
- do not rely on the bare model string as the unique identity when multiple providers may expose the same model
- encode provider identity into the option value for ambiguous entries (for example
@provider:model)
- do this even when
active_provider is None
- make sure named custom-provider groups also go through the same uniqueness logic
- optionally add a defensive duplicate-value check when building the dropdown/API response
That would preserve provider identity across:
- visual selection state
- click handling
- session persistence
- backend provider/model resolution
Local evidence
From my current local /api/models response:
active_provider = None
gpt-5.4 => ['custom:edith', 'openai-codex']
This duplicate is enough to reproduce the bug in the session model picker.
Summary
When multiple providers expose the same model ID/name in the conversation model picker, the picker loses provider identity.
In my current local setup,
/api/modelsreturns a duplicate model ID:custom:edith->gpt-5.4openai-codex->gpt-5.4This causes two visible problems in the chat/session model picker:
In practice it looks like the UI always keeps using the first matching provider entry.
Reproduction
custom:edithandopenai-codexboth exposegpt-5.4gpt-5.4entry under one providergpt-5.4entry under the other providerActual behavior
Expected behavior
Each provider/model entry should be uniquely selectable, even when multiple providers expose the same upstream model string.
Root cause from source inspection
This looks like a provider-identity bug caused by using the raw model string as the only option identity.
1.
/api/modelscan emit duplicate IDs across providersIn
api/config.py,_apply_provider_prefix()skips prefixing whenactive_provideris empty:So if
active_providerisNone, multiple providers can emit the same bare ID unchanged.2. Named custom provider groups also bypass provider-unique encoding
In the named custom-provider group path, models are appended as raw IDs and then added directly to
groups:That means a named custom provider like
custom:edithcan contribute baregpt-5.4, which then collides with another provider group that also exposes baregpt-5.4.3. The frontend selection state is keyed only by
valueIn
static/ui.js, the custom dropdown marks rows active only by comparingm.value === sel.value:So once two rows share the same
value, both rows render active.4. Clicking a duplicate value is treated as "no change"
selectModelFromDropdown()returns early when the current<select>already has the same raw value:So clicking the other provider's copy of the same value does not switch anything.
5. Session persistence only stores the raw model string
static/boot.jssends onlymodel:selectedModelto/api/session/update:So once provider identity is lost in the dropdown value, it is also lost in session persistence / backend resolution.
Why this seems distinct from earlier related issues
This looks related to earlier provider-routing / dedup work such as #138 and #907, but the current bug is specifically about duplicate option values across providers in the session model picker, which breaks:
Suggested fix direction
I think the safest fix is: ensure every provider/model entry in the picker has a globally unique value.
Concretely:
@provider:model)active_providerisNoneThat would preserve provider identity across:
Local evidence
From my current local
/api/modelsresponse:This duplicate is enough to reproduce the bug in the session model picker.