diff --git a/hermes_cli/doctor.py b/hermes_cli/doctor.py index fce081ebe0b6..f1dd38bb9ad0 100644 --- a/hermes_cli/doctor.py +++ b/hermes_cli/doctor.py @@ -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: diff --git a/tests/hermes_cli/test_doctor.py b/tests/hermes_cli/test_doctor.py index 2fa798a08159..c439aaf0cee5 100644 --- a/tests/hermes_cli/test_doctor.py +++ b/tests/hermes_cli/test_doctor.py @@ -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)