fix(doctor): UTF-8/latin-1 fallback when scanning .env

Prefer UTF-8 for ~/.hermes/.env provider scans, then latin-1 for cp1252/Notepad files. Add regression test for invalid UTF-8 bytes.
This commit is contained in:
Tianworld 2026-07-19 19:33:42 +08:00 committed by Teknium
parent 8dc9978741
commit 60dbce7c34
2 changed files with 31 additions and 5 deletions

View file

@ -887,11 +887,13 @@ def run_doctor(args):
if env_path.exists():
check_ok(f"{_DHH}/.env file exists")
# Check for common issues. Pin encoding to UTF-8 because .env files are
# written as UTF-8 everywhere in the codebase, while Path.read_text()
# defaults to the system locale — which crashes on non-UTF-8 Windows
# locales (e.g. GBK) as soon as the file contains any non-ASCII byte.
content = env_path.read_text(encoding="utf-8")
# Prefer UTF-8 (.env is written as UTF-8 elsewhere). Fall back to
# latin-1 for Windows Notepad/cp1252 files that are not valid UTF-8 —
# matches hermes_cli.env_loader._load_dotenv_with_fallback.
try:
content = env_path.read_text(encoding="utf-8")
except UnicodeDecodeError:
content = env_path.read_text(encoding="latin-1")
if _has_provider_env_config(content):
check_ok("API key or custom endpoint configured")
else:

View file

@ -126,6 +126,30 @@ class TestDoctorEnvFileEncoding:
doctor_mod.run_doctor(Namespace(fix=False))
def test_doctor_reads_invalid_utf8_env_via_latin1_fallback(
self, monkeypatch, tmp_path
):
"""cp1252/latin-1 .env with ASCII provider hints must not abort doctor."""
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
env_path = hermes_home / ".env"
# 0xff is invalid UTF-8; latin-1 decodes it. Keep an ASCII provider key
# so the scan still reports a configured endpoint/key.
env_path.write_bytes(b"OPENAI_API_KEY=sk-test\xff\n")
monkeypatch.setattr(doctor_mod, "HERMES_HOME", hermes_home)
fake_model_tools = types.SimpleNamespace(
check_tool_availability=lambda *a, **kw: (_ for _ in ()).throw(SystemExit(0)),
TOOLSET_REQUIREMENTS={},
)
monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools)
with pytest.raises(SystemExit):
doctor_mod.run_doctor(Namespace(fix=False))
class TestDoctorToolAvailabilityOverrides:
def test_marks_honcho_available_when_configured(self, monkeypatch):
monkeypatch.setattr(doctor, "_honcho_is_configured_for_doctor", lambda: True)