From e72abb593da7af6f1600b207cd378ef4656541f6 Mon Sep 17 00:00:00 2001 From: LHMQ878 <72402929@cityu-dg.edu.cn> Date: Mon, 27 Jul 2026 18:31:31 +0800 Subject: [PATCH] Fix UnicodeEncodeError in print_banner when stdout is non-UTF-8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The startup banner contains '➜' (U+279C). When stdout is redirected to a file or pipe on Windows, Python uses the system locale encoding (e.g. GBK/cp936) instead of UTF-8, so printing the banner raises UnicodeEncodeError and kills the process before the web server binds its port. Wrap banner output in a _safe_print helper that falls back to the stream's encoding with errors='replace' on UnicodeEncodeError, so startup continues regardless of stdout encoding. UTF-8 terminals are unaffected. Fixes #2532 --- src/kimi_cli/utils/server.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/kimi_cli/utils/server.py b/src/kimi_cli/utils/server.py index a33186ace5..30658f1e5d 100644 --- a/src/kimi_cli/utils/server.py +++ b/src/kimi_cli/utils/server.py @@ -4,6 +4,7 @@ import importlib import socket +import sys import textwrap @@ -86,6 +87,25 @@ def get_network_addresses() -> list[str]: return addresses +def _safe_print(text: str) -> None: + """Print *text*, degrading gracefully when stdout can't encode a character. + + When stdout is redirected to a file or pipe on Windows, Python uses the + system locale encoding (e.g. GBK/cp936) instead of UTF-8. Banner characters + such as ``➜`` (U+279C) are not representable there, so a plain ``print`` + raises ``UnicodeEncodeError`` and kills the process before the server binds + its port. Fall back to the stream's encoding with ``errors="replace"`` so + the banner still prints (with a placeholder) and startup continues. + """ + try: + print(text) + except UnicodeEncodeError: + stream = sys.stdout + encoding = getattr(stream, "encoding", None) or "utf-8" + safe = text.encode(encoding, errors="replace").decode(encoding) + print(safe) + + def print_banner(lines: list[str]) -> None: """Print a boxed banner with tag conventions (
, ,
).""" processed: list[str] = [] @@ -106,16 +126,16 @@ def strip_tags(s: str) -> str: width = max(60, *(len(line) for line in content_lines)) top = "+" + "=" * (width + 2) + "+" - print(top) + _safe_print(top) for line in processed: if line == "
": - print("|" + "-" * (width + 2) + "|") + _safe_print("|" + "-" * (width + 2) + "|") elif line.startswith("
"): content = line.removeprefix("
") - print(f"| {content.center(width)} |") + _safe_print(f"| {content.center(width)} |") elif line.startswith(""): content = line.removeprefix("") - print(f"| {content.ljust(width)} |") + _safe_print(f"| {content.ljust(width)} |") else: - print(f"| {line.ljust(width)} |") - print(top) + _safe_print(f"| {line.ljust(width)} |") + _safe_print(top)