# NousResearch/hermes-agent · UTF-8 encoding fixes for Windows config/file reads

## Open now

- 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.

## TL;DR

- 7 member(s): 0 open issue(s), 2 closed issue(s), 0 open PR(s), 2 merged PR(s), 3 PR(s) closed without merging.
- A fix was merged to main in PR #7174. Verify it addresses your case.
- **Recommended:** read PR #7174 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-45937`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member 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.

## Closed / duplicate issues

- #7058: \[Bug\]: Failed to load config.yml in Windows
- #45932: \[Bug\]消息网关 启动不了 咋解决?

## Prior & competing PRs

- **#7174: fix: four small CLI/config fixes (Bucket K salvage)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `agent/rate_limit_tracker.py`, `agent/smart_model_routing.py`, `cli.py`, `hermes_cli/gateway.py`
- **#13593: fix(encoding): add UTF-8 encoding for Windows compatibility (#7537, #8901, #7058)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#13986`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#45937: fix: read dashboard spa assets as utf-8**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `hermes_cli/web_server.py`, `tests/hermes_cli/test_web_server.py`
  - fix hunks:
  - `hermes_cli/web_server.py`

    ```diff
    @@ -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"
    ```

    ```diff
    @@ -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`

    ```diff
    @@ -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
    ```
- **#7063: fix(cli): Specify UTF-8 encoding when opening config file**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#7174`
- **#46017: fix(web_server): use UTF-8 encoding for dashboard reads on Windows**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#45937`

## Fix status

- Merged to main in PR #7174 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-14`.
- Subsystem: `hermes_cli`
