diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 0a98ad6e457b..3d4b7b504b53 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -2435,7 +2435,22 @@ You are Hermes Agent, an intelligent AI assistant created by Nous Research. You $pythonExe = "$InstallDir\venv\Scripts\python.exe" if (Test-Path $pythonExe) { try { - & $pythonExe "$InstallDir\tools\skills_sync.py" 2>$null + # Force the child python.exe to emit UTF-8 on its stdout/stderr. + # On non-UTF-8 Windows locales (CP936/GBK zh-CN) Python defaults + # its stream encoding to the active codepage and crashes on glyphs + # like ✓ (U+2713) that the codepage can't encode; the resulting + # non-UTF-8 bytes break this script's JSON result frame on stdout + # and abort the config-templates stage. Scope to this call only. + $prevPythonioencoding = $env:PYTHONIOENCODING + $prevPythonutf8 = $env:PYTHONUTF8 + $env:PYTHONIOENCODING = "utf-8" + $env:PYTHONUTF8 = "1" + try { + & $pythonExe "$InstallDir\tools\skills_sync.py" 2>$null + } finally { + $env:PYTHONIOENCODING = $prevPythonioencoding + $env:PYTHONUTF8 = $prevPythonutf8 + } Write-Success "Skills synced to $HermesHome\skills" } catch { # Fallback: simple directory copy diff --git a/tools/skills_sync.py b/tools/skills_sync.py index 09b8337540b0..5a343cd80e97 100644 --- a/tools/skills_sync.py +++ b/tools/skills_sync.py @@ -26,8 +26,24 @@ import json import logging import os import shutil +import sys from datetime import datetime, timezone from pathlib import Path, PurePosixPath + +# Force stdout/stderr to UTF-8. On non-UTF-8 Windows locales (e.g. CP936/GBK +# on zh-CN), Python's default stream encoding can't represent the checkmark / +# arrow glyphs this script prints (✓ U+2713, ↑ U+2191), raising +# UnicodeEncodeError mid-run. The bootstrap installer (install.ps1) captures +# this script's stdout and parses it as UTF-8; a GBK byte stream then surfaces +# as "stream did not contain valid UTF-8" and aborts the config-templates +# stage even though the script itself exits 0. Reconfigure unconditionally so +# output is valid UTF-8 regardless of the active codepage or caller. +for _stream in (sys.stdout, sys.stderr): + if hasattr(_stream, "reconfigure"): + try: + _stream.reconfigure(encoding="utf-8", errors="replace") + except (ValueError, TypeError): + pass from hermes_constants import get_bundled_skills_dir, get_hermes_home, get_optional_skills_dir from agent.skill_utils import is_excluded_skill_path from typing import Dict, List, Optional, Set, Tuple