diff --git a/lib/crewai/src/crewai/events/utils/console_formatter.py b/lib/crewai/src/crewai/events/utils/console_formatter.py index 858dde0acc..4d3358f6eb 100644 --- a/lib/crewai/src/crewai/events/utils/console_formatter.py +++ b/lib/crewai/src/crewai/events/utils/console_formatter.py @@ -41,6 +41,11 @@ class ConsoleFormatter: def __init__(self, verbose: bool = False): self.console = Console(width=None) + if hasattr(self.console.file, "reconfigure"): + try: + self.console.file.reconfigure(errors="replace") + except Exception: # noqa: S110 - best-effort; stream may not support reconfigure + pass self.verbose = verbose self._streaming_live: Live | None = None self._is_streaming: bool = False diff --git a/lib/crewai/tests/utilities/test_console_formatter_encoding.py b/lib/crewai/tests/utilities/test_console_formatter_encoding.py new file mode 100644 index 0000000000..6e76772cb5 --- /dev/null +++ b/lib/crewai/tests/utilities/test_console_formatter_encoding.py @@ -0,0 +1,25 @@ +import io + +from rich.text import Text + +from crewai.events.utils.console_formatter import ConsoleFormatter + + +class TestConsoleFormatterEncoding: + """Regression tests: emoji panel titles must not crash on non-UTF-8 streams (e.g. Windows cp1252 console).""" + + def test_reconfigures_stream_errors_to_replace(self): + formatter = ConsoleFormatter() + + assert formatter.console.file.errors == "replace" + + def test_emoji_panel_does_not_raise_on_cp1252_stream(self): + cp1252_stream = io.TextIOWrapper( + io.BytesIO(), encoding="cp1252", write_through=True + ) + formatter = ConsoleFormatter() + formatter.console.file = cp1252_stream + if hasattr(cp1252_stream, "reconfigure"): + cp1252_stream.reconfigure(errors="replace") + + formatter.print_panel(Text("x"), "🌊 Flow Started", "blue", is_flow=True)