mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-25 17:18:11 +00:00
Windows Notepad and PowerShell 5.1 Set-Content -Encoding UTF8 write a
leading UTF-8 BOM. json.load under encoding=utf-8 raises
JSONDecodeError("Unexpected UTF-8 BOM"), and load_jobs wraps that as
RuntimeError("Cron database corrupted and unrepairable"), taking down
cron CRUD/scheduler for a hand-edited jobs.json.
Read with utf-8-sig on all four independent jobs.json readers
(load_jobs primary + strict=False repair, dump _cron_summary, status
Scheduled Jobs). Write path stays plain utf-8 so the next save_jobs
heals a BOM'd file. Matches the env-class dialect (#65123).
Tests: BOM load (crash repro), bomless regression, empty store,
BOM+bare-list auto-repair, BOM+control-char strict=False arm, dump and
status CLI readers.
68 lines
2.6 KiB
Python
68 lines
2.6 KiB
Python
"""UTF-8 BOM tolerance for independent jobs.json readers (dump/status).
|
|
|
|
cron/jobs.load_jobs is covered in tests/cron/test_jobs.py. dump and status
|
|
each open jobs.json themselves — keep them on the same utf-8-sig dialect.
|
|
"""
|
|
|
|
from types import SimpleNamespace
|
|
|
|
|
|
def test_dump_cron_summary_accepts_utf8_bom(tmp_path):
|
|
from hermes_cli.dump import _cron_summary
|
|
|
|
cron = tmp_path / "cron"
|
|
cron.mkdir()
|
|
(cron / "jobs.json").write_bytes(
|
|
b'\xef\xbb\xbf{"jobs": [{"id": "j1", "enabled": true},'
|
|
b' {"id": "j2", "enabled": false}]}'
|
|
)
|
|
|
|
assert _cron_summary(tmp_path) == "1 active / 2 total"
|
|
|
|
|
|
def test_dump_cron_summary_bomless_regression(tmp_path):
|
|
from hermes_cli.dump import _cron_summary
|
|
|
|
cron = tmp_path / "cron"
|
|
cron.mkdir()
|
|
(cron / "jobs.json").write_text(
|
|
'{"jobs": [{"id": "j1", "enabled": true}]}',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
assert _cron_summary(tmp_path) == "1 active / 1 total"
|
|
|
|
|
|
def test_status_scheduled_jobs_accepts_utf8_bom(monkeypatch, capsys, tmp_path):
|
|
"""hermes status must not print '(error reading jobs file)' under BOM."""
|
|
from hermes_cli import status as status_mod
|
|
import hermes_cli.auth as auth_mod
|
|
import hermes_cli.gateway as gateway_mod
|
|
|
|
cron = tmp_path / "cron"
|
|
cron.mkdir()
|
|
(cron / "jobs.json").write_bytes(
|
|
b'\xef\xbb\xbf{"jobs": [{"id": "j1", "enabled": true},'
|
|
b' {"id": "j2", "enabled": true}]}'
|
|
)
|
|
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
|
monkeypatch.setattr(status_mod, "get_env_path", lambda: tmp_path / ".env", raising=False)
|
|
monkeypatch.setattr(status_mod, "get_hermes_home", lambda: tmp_path, raising=False)
|
|
monkeypatch.setattr(status_mod, "load_config", lambda: {"model": "gpt-5.4"}, raising=False)
|
|
monkeypatch.setattr(
|
|
status_mod, "resolve_requested_provider", lambda requested=None: "openai-codex", raising=False
|
|
)
|
|
monkeypatch.setattr(
|
|
status_mod, "resolve_provider", lambda requested=None, **kwargs: "openai-codex", raising=False
|
|
)
|
|
monkeypatch.setattr(status_mod, "provider_label", lambda provider: "OpenAI Codex", raising=False)
|
|
monkeypatch.setattr(auth_mod, "get_nous_auth_status", lambda: {}, raising=False)
|
|
monkeypatch.setattr(auth_mod, "get_codex_auth_status", lambda: {}, raising=False)
|
|
monkeypatch.setattr(auth_mod, "get_xai_oauth_auth_status", lambda: {}, raising=False)
|
|
monkeypatch.setattr(gateway_mod, "find_gateway_pids", lambda exclude_pids=None: [], raising=False)
|
|
|
|
status_mod.show_status(SimpleNamespace(all=False, deep=False))
|
|
out = capsys.readouterr().out
|
|
assert "(error reading jobs file)" not in out
|
|
assert "2 active, 2 total" in out
|