fix(auth): read .env as utf-8-sig in the dotenv-vs-shell detector
_remove_env_source() decides whether a credential var lives in ~/.hermes/.env
or the shell by scanning the .env with env_path.read_text(errors="replace") —
no encoding. read_text() with no encoding falls back to the system locale
(cp1252/GBK on Windows) and never strips a BOM.
The canonical .env readers in hermes_cli/config.py all use
encoding="utf-8-sig" precisely because 'users may edit .env in Notepad which
adds one' (a BOM), and doctor.py documents that .env is written as UTF-8
everywhere. This sibling reader diverged: on a Notepad-edited .env the BOM
prefixes the first line, so line.strip().startswith(f"{env_var}=") is False
for the first variable — the detector reports a .env-backed key as a phantom
shell export and prints a misleading 'still set in your shell environment'
hint on .
Match the canonical reader (utf-8-sig + errors=replace). Adds a regression
test with a BOM'd .env.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
Teknium
parent
3fee5c291b
commit
7fef76a6cf
@@ -162,9 +162,17 @@ def _remove_env_source(provider: str, removed) -> RemovalResult:
|
||||
try:
|
||||
env_path = get_env_path()
|
||||
if env_path.exists():
|
||||
# Read the .env as UTF-8 with BOM tolerance, matching the
|
||||
# canonical reader in hermes_cli/config.py. read_text() with no
|
||||
# encoding falls back to the system locale (cp1252/GBK on Windows)
|
||||
# and never strips a BOM, so a Notepad-edited .env (BOM + non-ASCII
|
||||
# values) would make the first line fail the startswith() check —
|
||||
# misreporting a .env-backed var as a shell export.
|
||||
env_in_dotenv = any(
|
||||
line.strip().startswith(f"{env_var}=")
|
||||
for line in env_path.read_text(errors="replace", encoding="utf-8").splitlines()
|
||||
for line in env_path.read_text(
|
||||
encoding="utf-8-sig", errors="replace"
|
||||
).splitlines()
|
||||
)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@@ -769,3 +769,44 @@ def test_auth_remove_copilot_suppresses_all_variants(tmp_path, monkeypatch):
|
||||
assert is_source_suppressed("copilot", "env:GITHUB_TOKEN")
|
||||
|
||||
|
||||
def test_auth_remove_env_seeded_dotenv_with_bom_no_shell_hint(tmp_path, monkeypatch, capsys):
|
||||
"""A Notepad-edited .env carries a UTF-8 BOM. The dotenv-vs-shell
|
||||
detector must still see the first variable as living in .env (and not
|
||||
warn about a phantom shell export). Regression for the reader that
|
||||
dropped encoding='utf-8-sig' and misread the BOM'd first line.
|
||||
"""
|
||||
hermes_home = tmp_path / "hermes"
|
||||
hermes_home.mkdir(parents=True, exist_ok=True)
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
|
||||
monkeypatch.delenv("DEEPSEEK_API_KEY", raising=False)
|
||||
# BOM prefix (utf-8-sig) + the target var as the FIRST line.
|
||||
(hermes_home / ".env").write_bytes(
|
||||
b"\xef\xbb\xbfDEEPSEEK_API_KEY=sk-ds-only\n"
|
||||
)
|
||||
monkeypatch.setenv("DEEPSEEK_API_KEY", "sk-ds-only")
|
||||
|
||||
_write_auth_store(
|
||||
tmp_path,
|
||||
{
|
||||
"version": 1,
|
||||
"credential_pool": {
|
||||
"deepseek": [{
|
||||
"id": "env-1",
|
||||
"label": "DEEPSEEK_API_KEY",
|
||||
"auth_type": "api_key",
|
||||
"priority": 0,
|
||||
"source": "env:DEEPSEEK_API_KEY",
|
||||
"access_token": "sk-ds-only",
|
||||
}]
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
from types import SimpleNamespace
|
||||
from hermes_cli.auth_commands import auth_remove_command
|
||||
auth_remove_command(SimpleNamespace(provider="deepseek", target="1"))
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert "Cleared DEEPSEEK_API_KEY from .env" in out
|
||||
assert "still set in your shell environment" not in out
|
||||
|
||||
Reference in New Issue
Block a user