mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(profile): read .env as utf-8-sig in the distribution-install preview
`_render_distribution_plan` reads the target profile's `.env` to decide whether a required env var is already set (so it doesn't nag the user), using `Path.read_text()` with no encoding. Two bugs: 1. `Path.read_text()` defaults to the system locale (cp1251/GBK on Windows), which raises `UnicodeDecodeError` on any non-ASCII byte. The surrounding `except OSError` does NOT catch that — `UnicodeDecodeError` is a `ValueError` — so a mis-encoded `.env` aborts the entire install preview. 2. Even on a UTF-8 locale, a Notepad-added BOM prefixes the first key (`KEY`), so the very first required env var is mis-reported as "needs setting" when it is actually present. `.env` is written as UTF-8 everywhere in the codebase. Read it as `utf-8-sig` (tolerates the BOM) and also catch `UnicodeDecodeError` so a genuinely un-decodable file skips the pre-check instead of crashing. Regression tests: a BOM-prefixed `.env` whose first key must still read as "set", and an invalid-UTF-8 `.env` that must not abort the preview.
This commit is contained in:
parent
f1ea4a56c2
commit
40828997a5
2 changed files with 76 additions and 1 deletions
|
|
@ -13365,7 +13365,10 @@ def _render_distribution_plan(plan) -> None:
|
|||
if key == er.name:
|
||||
already = True
|
||||
break
|
||||
except OSError:
|
||||
except (OSError, UnicodeDecodeError):
|
||||
# UnicodeDecodeError is a ValueError, not an OSError, so
|
||||
# the old guard let a mis-encoded .env abort the whole
|
||||
# install preview. Skip the pre-check instead.
|
||||
pass
|
||||
status = "✓ set" if already else ("needs setting" if er.required else "—")
|
||||
line = f" • {er.name} ({tag}, {status})"
|
||||
|
|
|
|||
72
tests/hermes_cli/test_profile_install_env_encoding.py
Normal file
72
tests/hermes_cli/test_profile_install_env_encoding.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
"""Regression: the distribution-install preview must read .env as UTF-8.
|
||||
|
||||
`_render_distribution_plan` inspects the target profile's `.env` to decide
|
||||
whether a required env var is already set, so it doesn't nag the user. It
|
||||
used `Path.read_text()` with no encoding, which:
|
||||
|
||||
1. defaults to the system locale (cp1251/GBK on Windows) and raises
|
||||
`UnicodeDecodeError` on any non-ASCII byte — and the surrounding
|
||||
`except OSError` does NOT catch that (it's a `ValueError`), so a
|
||||
mis-encoded .env aborts the whole install preview; and
|
||||
2. even on a UTF-8 locale, a Notepad-added BOM prefixes the first key,
|
||||
so the very first env var is mis-detected as "needs setting".
|
||||
|
||||
The fix reads `utf-8-sig` and also catches `UnicodeDecodeError`.
|
||||
"""
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from hermes_cli.main import _render_distribution_plan
|
||||
|
||||
|
||||
def _make_plan(target_dir, env_requires):
|
||||
manifest = SimpleNamespace(
|
||||
name="demo",
|
||||
version="1.0.0",
|
||||
description="",
|
||||
author="",
|
||||
hermes_requires="",
|
||||
env_requires=env_requires,
|
||||
)
|
||||
return SimpleNamespace(
|
||||
manifest=manifest,
|
||||
provenance="local",
|
||||
target_dir=target_dir,
|
||||
existing=False,
|
||||
has_cron=False,
|
||||
)
|
||||
|
||||
|
||||
def test_bom_prefixed_env_first_key_is_detected_as_set(tmp_path, monkeypatch, capsys):
|
||||
"""A required key on the first line of a BOM-prefixed .env reads as set."""
|
||||
monkeypatch.delenv("FOO_TOKEN", raising=False)
|
||||
profile = tmp_path / "profile"
|
||||
profile.mkdir()
|
||||
# utf-8-sig write == UTF-8 with a leading BOM, exactly what Notepad emits.
|
||||
(profile / ".env").write_text("FOO_TOKEN=abc\nBAR=1\n", encoding="utf-8-sig")
|
||||
|
||||
plan = _make_plan(profile, [SimpleNamespace(name="FOO_TOKEN", required=True, description="")])
|
||||
_render_distribution_plan(plan)
|
||||
|
||||
out = capsys.readouterr().out
|
||||
foo_line = next(ln for ln in out.splitlines() if "FOO_TOKEN" in ln)
|
||||
assert "✓ set" in foo_line, f"BOM must not hide the first key: {foo_line!r}"
|
||||
|
||||
|
||||
def test_non_utf8_env_does_not_abort_the_preview(tmp_path, monkeypatch, capsys):
|
||||
"""A .env with invalid UTF-8 bytes must not crash the install preview."""
|
||||
monkeypatch.delenv("FOO_TOKEN", raising=False)
|
||||
profile = tmp_path / "profile"
|
||||
profile.mkdir()
|
||||
# \xff is an invalid UTF-8 start byte -> UnicodeDecodeError on read.
|
||||
(profile / ".env").write_bytes(b"FOO_TOKEN=\xff\xfe bad\n")
|
||||
|
||||
plan = _make_plan(profile, [SimpleNamespace(name="FOO_TOKEN", required=True, description="")])
|
||||
|
||||
# Must return normally (old `except OSError` let UnicodeDecodeError escape).
|
||||
_render_distribution_plan(plan)
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert any("FOO_TOKEN" in ln for ln in out.splitlines())
|
||||
Loading…
Add table
Add a link
Reference in a new issue