UTF-8 encoding fixes for Windows config/file reads

NousResearch/hermes-agent · cluster 7058 · hermes cli

Data as of

Nothing open on GitHub. Fix merged in #7174. Check it covers your case.

The short answer

0open issues
0open PRs
7items grouped
Known?
Yes. 7 related items grouped here, 2 issues already closed.
Fix event
A fix was merged to main in PR #7174. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PR #45937. That is separate from the fix event.
Still open
Nothing open on GitHub.

Multiple bug reports and fixes addressing UTF-8 encoding issues when reading config files, dashboard assets, and other I/O on Windows. Changes add explicit encoding declarations across CLI, web server, and config handling code.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #7174 fix: four small CLI/config fixes (Bucket K salvage)

    merged to main

    files: agent/rate_limit_tracker.py, agent/smart_model_routing.py, cli.py, hermes_cli/gateway.py

  • #45937 fix: read dashboard spa assets as utf-8

    merged to main

    files: hermes_cli/web_server.py, tests/hermes_cli/test_web_server.py

    Show the diff
    • hermes_cli/web_server.py
      @@ -10625,7 +10625,7 @@ def _serve_index(prefix: str = ""):
               ``__HERMES_AUTH_REQUIRED__`` flag lets the SPA pick the right
               auth scheme for /api/pty and /api/ws (ticket vs token).
               """
      -        html = _index_path.read_text()
      +        html = _index_path.read_text(encoding="utf-8")
               chat_js = "true" if _DASHBOARD_EMBEDDED_CHAT_ENABLED else "false"
               gated = bool(getattr(app.state, "auth_required", False))
               gated_js = "true" if gated else "false"
      @@ -10675,7 +10675,7 @@ async def serve_css(filename: str, request: Request):
               ):
                   return JSONResponse({"error": "not found"}, status_code=404)
               prefix = _normalise_prefix(request.headers.get("x-forwarded-prefix"))
      -        css = css_path.read_text()
      +        css = css_path.read_text(encoding="utf-8")
               if prefix:
                   for asset_dir in ("/fonts/", "/fonts-terminal/", "/ds-assets/", "/assets/"):
                       css = css.replace(f"url({asset_dir}", f"url({prefix}{asset_dir}")
    • tests/hermes_cli/test_web_server.py
      @@ -1857,6 +1857,45 @@ def test_path_traversal_dotdot_blocked(self):
               if resp.status_code == 200:
                   assert "FastAPI" not in resp.text  # Should not serve the actual source
       
      +    def test_spa_assets_are_read_as_utf8(self, monkeypatch, tmp_path):
      +        from fastapi import FastAPI
      +        from starlette.testclient import TestClient
      +        import hermes_cli.web_server as ws
      +
      +        dist = tmp_path / "web_dist"
      +        assets = dist / "assets"
      +        assets.mkdir(parents=True)
      +        index_path = dist / "index.html"
      +        css_path = assets / "app.css"
      +        index_path.write_text("<html><head></head><body>cafe cafe</body></html>", encoding="utf-8")
      +        css_path.write_text("body::before { content: 'cafe'; }", encoding="utf-8")
      +
      +        original_read_text = Path.read_text
      +        seen_encodings = {}
      +
      +        def tracking_read_text(path_self, *args, **kwargs):
      +            if path_self == index_path:
      +                seen_encodings["index"] = kwargs.get("encoding")
      +            elif path_self == css_path:
      +                seen_encodings["css"] = kwargs.get("encoding")
      +            return original_read_text(path_self, *args, **kwargs)
      +
      +        monkeypatch.setattr(ws, "WEB_DIST", dist)
      +        monkeypatch.setattr(Path, "read_text", tracking_read_text)
      +        spa_app = FastAPI()
      +        ws.mount_spa(spa_app)
      +        spa_client = TestClient(spa_app)
      +
      +        index_resp = spa_client.get("/chat")
      +        assert index_resp.status_code == 200
      +        assert "cafe cafe" in index_resp.text
      +
      +        css_resp = spa_client.get("/assets/app.css", headers={"x-forwarded-prefix": "/hermes"})
      +        assert css_resp.status_code == 200
      +        assert "content: 'cafe';" in css_resp.text
      +
      +        assert seen_encodings == {"index": "utf-8", "css": "utf-8"}
      +
           def test_set_model_main_nous_applies_gateway_defaults(self, monkeypatch):
               """Switching the main provider to Nous calls apply_nous_managed_defaults
               (mirroring the CLI's post-model-selection Tool Gateway routing) and

3 PRs closed without merging

From the close comments: duplicate 1 · merged via another change 1 · superseded by another PR 1

  • #7063 fix(cli): Specify UTF-8 encoding when opening config file · merged via another change → #7174
  • #13593 fix(encoding): add UTF-8 encoding for Windows compatibility (#7537, #8901, #7058) · superseded by another PR → #13986
  • #46017 fix(web_server): use UTF-8 encoding for dashboard reads on Windows · duplicate → #45937
Closed issues · 2
  • Closed#7058[Bug]: Failed to load config.yml in Windows
  • Closed#45932[Bug]消息网关 启动不了 咋解决?
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.26: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
hermes cli
Merged PRs
#7174, #45937
Closed issues
2
Attempts
3 PRs closed without merging
Fix link checked
14 Jun 2026

For your agent

The same facts, typed.