fix(config): tolerate a non string default models value

default_models and default_pinned_models are stored as JSON and read as
comma separated strings. Nothing coerces them on the way out, so a value
that is not a string reaches the browser unchanged and every consumer that
calls split on it throws.

In v0.10.2 the only call site ran later in the chat flow, so a malformed
value sat there unnoticed. v0.11.0 added a second call site that runs while
the chat component initialises, which turned the same stored value into an
uncaught TypeError on New Chat: the route changed but the previous chat
stayed on screen and no new chat was created. Rolling back appeared to fix
it because the new call site was gone, not because the value had changed.

Both keys are now normalised where they are read. A list is joined back
into the comma separated form the readers expect, a string passes through,
and anything else becomes None. This covers the app config the browser
consumes, the admin models config endpoints, and the custom model fallback
lookup, which called split on the raw value as well.

The admin UI and the models config endpoint already write these keys as
strings, so this only concerns values that reached the database another
way, such as a config import, which upserts its payload without per key
validation.
This commit is contained in:
silentoplayz 2026-08-08 09:54:39 -04:00
parent 8faaf2cd1e
commit 5eb684fbc9
3 changed files with 23 additions and 6 deletions

View file

@ -1640,6 +1640,15 @@ DEFAULT_MODELS = os.getenv('DEFAULT_MODELS', None)
DEFAULT_PINNED_MODELS = os.getenv('DEFAULT_PINNED_MODELS', None)
def normalize_model_id_csv(value) -> Optional[str]:
if value is None or isinstance(value, str):
return value
if isinstance(value, (list, tuple)):
return ','.join(str(model_id).strip() for model_id in value if model_id)
return None
try:
default_prompt_suggestions = JSONCodec.loads(os.getenv('DEFAULT_PROMPT_SUGGESTIONS', '[]'))
except Exception as e:

View file

@ -67,6 +67,7 @@ from open_webui.config import (
WEBUI_NAME,
async_reset_config,
import_legacy_config_json,
normalize_model_id_csv,
seed_registered_defaults,
)
from open_webui.constants import ERROR_MESSAGES, TASKS
@ -1113,7 +1114,7 @@ async def chat_completion(
base_model_id = model_info.base_model_id
if base_model_id not in request.app.state.MODELS:
if ENABLE_CUSTOM_MODEL_FALLBACK:
default_models = ((await Config.get('ui.default_models')) or '').split(',')
default_models = (normalize_model_id_csv(await Config.get('ui.default_models')) or '').split(',')
fallback_model_id = default_models[0].strip() if default_models[0] else None
@ -2234,8 +2235,8 @@ async def get_app_config(request: Request):
},
**(
{
'default_models': config.get('ui.default_models'),
'default_pinned_models': config.get('ui.default_pinned_models'),
'default_models': normalize_model_id_csv(config.get('ui.default_models')),
'default_pinned_models': normalize_model_id_csv(config.get('ui.default_pinned_models')),
'default_prompt_suggestions': config.get('ui.prompt_suggestions'),
**({'user_count': user_count} if user_count is not None else {}),
'code': {

View file

@ -7,7 +7,7 @@ from typing import Optional
import aiohttp
from fastapi import APIRouter, Depends, HTTPException, Request
from mcp.shared.auth import OAuthMetadata
from open_webui.config import BannerModel
from open_webui.config import BannerModel, normalize_model_id_csv
from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_TIMEOUT
from open_webui.events import EVENTS, publish_event
from open_webui.models.config import Config
@ -740,15 +740,22 @@ async def get_models_defaults(request: Request, user=Depends(get_verified_user))
}
def normalize_models_config_values(values: dict) -> dict:
for field in ('DEFAULT_MODELS', 'DEFAULT_PINNED_MODELS'):
if field in values:
values[field] = normalize_model_id_csv(values[field])
return values
@router.get('/models', response_model=ModelsConfigForm)
async def get_models_config(request: Request, user=Depends(get_admin_user)):
return await get_config_values(MODELS_CONFIG_KEYS)
return normalize_models_config_values(await get_config_values(MODELS_CONFIG_KEYS))
@router.post('/models', response_model=ModelsConfigForm)
async def set_models_config(request: Request, form_data: ModelsConfigForm, user=Depends(get_admin_user)):
await Config.upsert(config_updates(form_data.model_dump(), MODELS_CONFIG_KEYS))
values = await get_config_values(MODELS_CONFIG_KEYS)
values = normalize_models_config_values(await get_config_values(MODELS_CONFIG_KEYS))
await publish_event(
request,
EVENTS.CONFIG_MODELS_UPDATED,