diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 45d1af0652..c022261bce 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -10699,7 +10699,9 @@ async def get_image(): # If UI_LOGO_PATH points to a local file, serve it directly (skip cache) if logo_path != default_logo and not logo_path.startswith(("http://", "https://")): - return FileResponse(logo_path, media_type="image/jpeg") + if os.path.exists(logo_path): + return FileResponse(logo_path, media_type="image/jpeg") + # Fall through to cache or default if custom path doesn't exist # [OPTIMIZATION] For HTTP URLs and default logo, check if the cached image exists if os.path.exists(cache_path): diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index 532e11e70f..e68dfedab5 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -3224,6 +3224,48 @@ async def test_get_image_default_logo_still_uses_cache(monkeypatch): ) +@pytest.mark.asyncio +async def test_get_image_custom_logo_missing_falls_through_to_default(monkeypatch): + """ + Test that when UI_LOGO_PATH points to a non-existent local file, + get_image falls through to the cache/default logo instead of failing. + """ + from unittest.mock import patch + + from litellm.proxy.proxy_server import get_image + + monkeypatch.setenv("UI_LOGO_PATH", "/app/nonexistent_logo.jpg") + monkeypatch.delenv("LITELLM_NON_ROOT", raising=False) + monkeypatch.delenv("LITELLM_ASSETS_PATH", raising=False) + + calls_to_file_response = [] + + def fake_file_response(path, **kwargs): + calls_to_file_response.append(path) + return MagicMock() + + def exists_side_effect(path): + # The custom logo does NOT exist; cache and default DO exist + if path == "/app/nonexistent_logo.jpg": + return False + return True + + with patch("litellm.proxy.proxy_server.os.path.exists", side_effect=exists_side_effect), \ + patch("litellm.proxy.proxy_server.os.access", return_value=True), \ + patch("litellm.proxy.proxy_server.FileResponse", side_effect=fake_file_response): + + await get_image() + + assert len(calls_to_file_response) == 1, "FileResponse should be called exactly once" + served_path = calls_to_file_response[0] + assert served_path != "/app/nonexistent_logo.jpg", ( + "Should not attempt to serve a non-existent custom logo" + ) + assert served_path.endswith("cached_logo.jpg"), ( + f"Expected fallback to cached_logo.jpg, got {served_path}" + ) + + def test_get_config_normalizes_string_callbacks(monkeypatch): """ Test that /get/config/callbacks normalizes string callbacks to lists.