From 516d18106d8336206e4e818e068b91113bba81fd Mon Sep 17 00:00:00 2001 From: Siddhanth Gupta Date: Wed, 4 Nov 2026 13:29:27 +0530 Subject: [PATCH] fix(cli): don't crash on redirected output with a legacy code page With output redirected on Windows, stdout uses cp1252 and printing the check mark raised UnicodeEncodeError, so even `concore init demo > f` exited 1. Set errors="replace" on stdout when the CLI is invoked so unencodable characters are replaced instead. --- concore_cli/cli.py | 5 ++++- tests/test_cli.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/concore_cli/cli.py b/concore_cli/cli.py index dc8fca3..f0a4559 100644 --- a/concore_cli/cli.py +++ b/concore_cli/cli.py @@ -21,7 +21,10 @@ @click.group() @click.version_option(version=__version__, prog_name="concore") def cli(): - pass + # Legacy code pages (e.g. cp1252 when output is redirected on Windows) can't + # encode symbols like the check mark; replace them instead of crashing. + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(errors="replace") @cli.command() diff --git a/tests/test_cli.py b/tests/test_cli.py index b95c60d..682212a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -882,6 +882,22 @@ def test_build_command_shared_source_specialization_merges_edge_params(self): self.assertIn("PORT_NAME_B_C", content) self.assertIn("PORT_B_C", content) + def test_cli_redirected_output_with_legacy_encoding(self): + env = dict(os.environ, PYTHONIOENCODING="cp1252") + repo_root = str(Path(__file__).resolve().parent.parent) + env["PYTHONPATH"] = repo_root + os.pathsep + env.get("PYTHONPATH", "") + result = subprocess.run( + [sys.executable, "-c", "from concore_cli.cli import cli; cli()"] + + ["init", "demo"], + cwd=self.temp_dir, + env=env, + stdin=subprocess.DEVNULL, + capture_output=True, + timeout=60, + ) + self.assertEqual(result.returncode, 0, result.stderr.decode(errors="replace")) + self.assertTrue((Path(self.temp_dir) / "demo" / "workflow.graphml").exists()) + def test_build_command_existing_output(self): with self.runner.isolated_filesystem(temp_dir=self.temp_dir): result = self.runner.invoke(cli, ["init", "test-project"])