From 52061954ad1cd9ebc6084a1270ba07822ae506ee Mon Sep 17 00:00:00 2001 From: 3aKHP <178400596+3aKHP@users.noreply.github.com> Date: Wed, 14 Oct 2026 18:03:58 +0800 Subject: [PATCH] fix(config): emit UTF-8 CLI output on Windows Configure the CLI process streams as UTF-8 so redirected Chinese JSON does not fail under cp1252. Exercise real CLI success and error output in subprocess regressions and Windows CI, and include the fix in v1.16.2 release notes. --- .github/workflows/ci.yml | 3 +- CHANGELOG.md | 1 + .../self-docs/references/root-changelog.md | 1 + src/quickquip/llm/config_cli.py | 2 ++ tests/unit/llm/test_config_cli_encoding.py | 34 +++++++++++++++++++ 5 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 tests/unit/llm/test_config_cli_encoding.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7485027..db4b4f6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,5 +36,6 @@ jobs: - name: Verify configuration initialization and backup/restore run: >- python -m pytest tests/unit/llm/test_config_service.py + tests/unit/llm/test_config_cli_encoding.py --basetemp "${{ runner.temp }}/pytest-quickquip" - -k "two_processes_initialize_one_database or backup_is_consistent_and_restore_clears_leases" + -k "two_processes_initialize_one_database or backup_is_consistent_and_restore_clears_leases or config_cli_emits_utf8" diff --git a/CHANGELOG.md b/CHANGELOG.md index 597a9698..8e9f1463 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ - 修复大体积 Trace 数据库重启后首次读取调用索引缓慢的问题,为待完成记录的过期查找增加索引。 - 修复 Windows WebView 启动器的 IPv6 后台访问链接。 - 修复 Windows 首次初始化和备份 LLM 配置数据库时的文件同步错误。 +- 配置 CLI 统一输出 UTF-8 JSON,修复 Windows 重定向中文结果时的编码错误。 - Windows 懒人包安装项目版本元数据,使 Bot 版本状态与请求身份使用实际发行版本。 - 私聊开启提示统一推荐 `/start_session`,历史拼写入口继续可用。 diff --git a/skills.example/self-docs/references/root-changelog.md b/skills.example/self-docs/references/root-changelog.md index 0fe5dd65..b5d9f43d 100644 --- a/skills.example/self-docs/references/root-changelog.md +++ b/skills.example/self-docs/references/root-changelog.md @@ -61,6 +61,7 @@ - 修复大体积 Trace 数据库重启后首次读取调用索引缓慢的问题,为待完成记录的过期查找增加索引。 - 修复 Windows WebView 启动器的 IPv6 后台访问链接。 - 修复 Windows 首次初始化和备份 LLM 配置数据库时的文件同步错误。 +- 配置 CLI 统一输出 UTF-8 JSON,修复 Windows 重定向中文结果时的编码错误。 - Windows 懒人包安装项目版本元数据,使 Bot 版本状态与请求身份使用实际发行版本。 - 私聊开启提示统一推荐 `/start_session`,历史拼写入口继续可用。 diff --git a/src/quickquip/llm/config_cli.py b/src/quickquip/llm/config_cli.py index 64aeea31..0f69608c 100644 --- a/src/quickquip/llm/config_cli.py +++ b/src/quickquip/llm/config_cli.py @@ -171,4 +171,6 @@ def main(argv=None) -> int: if __name__ == "__main__": + sys.stdout.reconfigure(encoding="utf-8") + sys.stderr.reconfigure(encoding="utf-8") raise SystemExit(main()) diff --git a/tests/unit/llm/test_config_cli_encoding.py b/tests/unit/llm/test_config_cli_encoding.py new file mode 100644 index 00000000..b953e6e8 --- /dev/null +++ b/tests/unit/llm/test_config_cli_encoding.py @@ -0,0 +1,34 @@ +"""Configuration CLI JSON remains readable when redirected on Windows.""" + +from __future__ import annotations + +import json +import os +from pathlib import Path +import subprocess +import sys + +import pytest + + +@pytest.mark.parametrize("command,exit_code", [("init", 0), ("validate", 1)]) +def test_config_cli_emits_utf8_on_legacy_console_encoding(tmp_path, command, exit_code): + database = tmp_path / "llm_config.db" + arguments = ["--db", str(database), command] + if command == "init": + arguments.extend(["--legacy", str(tmp_path / "missing.toml")]) + env = {**os.environ, "PYTHONIOENCODING": "cp1252", "PYTHONUTF8": "0"} + env["PYTHONPATH"] = str(Path(__file__).resolve().parents[3] / "src") + result = subprocess.run( + [sys.executable, "-m", "quickquip.llm.config_cli", *arguments], + cwd=tmp_path, env=env, capture_output=True, timeout=30, + ) + assert result.returncode == exit_code, result.stderr.decode("utf-8") + output = (result.stdout if exit_code == 0 else result.stderr).decode("utf-8") + payload = json.loads(output) + assert any(ord(character) > 127 for character in output) + if command == "init": + assert payload["revision"] == 1 + assert database.is_file() + else: + assert payload["error"]["code"] == "not_initialized"