From ae9aad8160618719cc547f5912fb07288725f8ba Mon Sep 17 00:00:00 2001 From: Arthi Arumugam Date: Mon, 27 Jul 2026 16:22:10 +0530 Subject: [PATCH] fix(proxy): start on consoles that cannot encode the startup banner show_banner is the first thing run_server does, and the banner is box-drawing characters. On a console whose encoding cannot represent them, which is the default on Windows, click.echo raises UnicodeEncodeError and the proxy exits with "Application startup failed" before it binds a port. Setting PYTHONIOENCODING=utf-8 works around it. The banner is decorative, so fall back to a plain "LiteLLM" when the stream cannot encode it rather than taking the server down with it. --- litellm/proxy/common_utils/banner.py | 2 ++ .../proxy/common_utils/test_banner.py | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 tests/test_litellm/proxy/common_utils/test_banner.py diff --git a/litellm/proxy/common_utils/banner.py b/litellm/proxy/common_utils/banner.py index 9983f128378..8f9590fa8a8 100644 --- a/litellm/proxy/common_utils/banner.py +++ b/litellm/proxy/common_utils/banner.py @@ -15,3 +15,5 @@ def show_banner(): click.echo(f"\n{LITELLM_BANNER}\n") except ImportError: print("\n") # noqa: T201 + except UnicodeEncodeError: + print("\n LiteLLM\n") # noqa: T201 diff --git a/tests/test_litellm/proxy/common_utils/test_banner.py b/tests/test_litellm/proxy/common_utils/test_banner.py new file mode 100644 index 00000000000..ce0915720ad --- /dev/null +++ b/tests/test_litellm/proxy/common_utils/test_banner.py @@ -0,0 +1,36 @@ +import io +import os +import sys +from contextlib import redirect_stdout + +sys.path.insert(0, os.path.abspath("../../../..")) + +from litellm.proxy.common_utils.banner import show_banner + + +def _cp1252_stdout() -> io.TextIOWrapper: + """A stdout that cannot encode the banner, like a Windows console on the default code page.""" + return io.TextIOWrapper(io.BytesIO(), encoding="cp1252", newline="") + + +class TestShowBanner: + def test_show_banner_survives_a_stdout_that_cannot_encode_it(self): + """The banner is decorative; an unencodable console must not stop the proxy from starting.""" + stream = _cp1252_stdout() + + with redirect_stdout(stream): + show_banner() + + stream.flush() + printed = stream.buffer.getvalue().decode("cp1252") + assert "LiteLLM" in printed + + def test_show_banner_prints_the_full_banner_when_stdout_can_encode_it(self): + stream = io.TextIOWrapper(io.BytesIO(), encoding="utf-8", newline="") + + with redirect_stdout(stream): + show_banner() + + stream.flush() + printed = stream.buffer.getvalue().decode("utf-8") + assert "██" in printed