Bug: _get_provider_base_url() crashes when providers is defined as a list in config.yaml
Description
The function _get_provider_base_url(provider_id) in api/config.py (line ~1921) assumes that cfg["providers"] is always a dict. When the user defines it as a list instead (providers: []), the function crashes with:
AttributeError: 'list' object has no attribute 'get'
Reproduction
- In
config.yaml, set: providers: [] (empty list)
- Start a chat that triggers
_get_provider_base_url()
- Result:
AttributeError: 'list' object has no attribute 'get'
Root Cause
The line:
prov_cfg = cfg.get("providers", {}).get(provider_id, {}) or {}
only works with a dict. An empty list [] is semantically identical to {} ("no providers configured"), but the code doesn't handle both forms.
Fix
def _get_provider_base_url(provider_id):
"""Look up the configured base_url for a provider (e.g. lmstudio).
Checks two locations, in order:
1. ``cfg["providers"][<provider_id>]["base_url"]`` — the explicit
per-provider override.
2. ``cfg["model"]["base_url"]`` — falls back here when
``cfg["model"]["provider"] == provider_id``. This is the historical
shape (the model block carries both the active provider AND the
base URL for that provider in a single record).
Returns the URL stripped of trailing ``/`` if configured, otherwise None.
"""
prov_raw = cfg.get("providers", {})
# providers can be a dict (provider_id -> config) or a list of provider entries
if isinstance(prov_raw, dict):
prov_cfg = prov_raw.get(provider_id, {}) or {}
elif isinstance(prov_raw, list):
# Search for matching provider in list format
prov_cfg = None
for entry in prov_raw:
if isinstance(entry, dict):
entry_id = str(entry.get("id", "") or entry.get("name", "")).strip().lower()
if entry_id == str(provider_id).strip().lower():
prov_cfg = entry
break
prov_cfg = prov_cfg or {}
else:
prov_cfg = {}
explicit = (prov_cfg.get("base_url") or "").strip().rstrip("/")
if explicit:
return explicit
model_cfg = cfg.get("model", {}) or {}
if isinstance(model_cfg, dict):
model_provider = str(model_cfg.get("provider") or "").strip().lower()
if model_provider == str(provider_id).strip().lower():
model_base = (model_cfg.get("base_url") or "").strip().rstrip("/")
if model_base:
return model_base
return None
Background
The bug has existed since commit 35cf332c ("feat: add LM Studio provider support with live model discovery"). Since then, 125+ commits touched config.py, but no one caught the list-form edge case.
Most users either have {} or don't set providers explicitly at all. Only in rare cases is a list used — and then the chat crashes.
Bug:
_get_provider_base_url()crashes whenprovidersis defined as a list in config.yamlDescription
The function
_get_provider_base_url(provider_id)inapi/config.py(line ~1921) assumes thatcfg["providers"]is always a dict. When the user defines it as a list instead (providers: []), the function crashes with:Reproduction
config.yaml, set:providers: [](empty list)_get_provider_base_url()AttributeError: 'list' object has no attribute 'get'Root Cause
The line:
only works with a dict. An empty list
[]is semantically identical to{}("no providers configured"), but the code doesn't handle both forms.Fix
Background
The bug has existed since commit
35cf332c("feat: add LM Studio provider support with live model discovery"). Since then, 125+ commits touched config.py, but no one caught the list-form edge case.Most users either have
{}or don't setprovidersexplicitly at all. Only in rare cases is a list used — and then the chat crashes.