Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions hermes_cli/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -791,11 +791,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:
Expand Down
24 changes: 24 additions & 0 deletions tests/hermes_cli/test_doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,30 @@ def gbk_like_read_text(self, encoding=None, errors=None, **kwargs):
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)
Expand Down
Loading