From 5eb684fbc9d5f45d21715c779ec135e5c00468b3 Mon Sep 17 00:00:00 2001 From: silentoplayz Date: Sat, 8 Aug 2026 09:54:39 -0400 Subject: [PATCH] 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. --- backend/open_webui/config.py | 9 +++++++++ backend/open_webui/main.py | 7 ++++--- backend/open_webui/routers/configs.py | 13 ++++++++++--- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index 365544e80c..0508580499 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -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: diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index f5c182eb3f..27493c919c 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -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': { diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py index f591908a6d..ac96ca4025 100644 --- a/backend/open_webui/routers/configs.py +++ b/backend/open_webui/routers/configs.py @@ -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,