# NousResearch/hermes-agent · Gateway JSON file UnicodeDecodeError handling

## Open now

- Nothing open on GitHub.

A bug was reported where corrupted or non-UTF-8 gateway state files cause unhandled UnicodeDecodeError exceptions and persistent warnings. Multiple proposed fixes address catching this error in _read_json_file and related gateway state reads.

## TL;DR

- 7 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 5 PR(s) closed without merging.
- A fix was merged to main in PR #39488. Verify it addresses your case.
- **Recommended:** read PR #39488 (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: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.29`: 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

- #28579: bug: _read_json_file doesn't catch UnicodeDecodeError — corrupted gateway_state.json causes persistent warnings

## Prior & competing PRs

- **#28599: fix: catch UnicodeDecodeError in _read_json_file for corrupted gateway_state.json**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `899ee8c23dfd029fdfd7b669ac3ac82fd8388f55`, `#28579`, `#28600`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#29208: fix(gateway): catch UnicodeDecodeError in _read_json_file (#28579)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `899ee8c23dfd029fdfd7b669ac3ac82fd8388f55`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#39488: fix(gateway): tolerate non-UTF-8 status/pid files in gateway status reads**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `gateway/status.py`, `tests/gateway/test_status.py`
  - fix hunks:
  - `gateway/status.py`

    ```diff
    @@ -227,7 +227,10 @@ def _read_json_file(path: Path) -> Optional[dict[str, Any]]:
             return None
         try:
             raw = path.read_text(encoding="utf-8").strip()
    -    except OSError:
    +    except (OSError, UnicodeDecodeError):
    +        # OSError: file vanished or permission flipped between exists() and
    +        # read. UnicodeDecodeError: file holds non-UTF-8 / binary garbage
    +        # (a truncated or clobbered status file). Either way it's unusable.
             return None
         if not raw:
             return None
    ```

    ```diff
    @@ -249,8 +252,9 @@ def _read_pid_record(pid_path: Optional[Path] = None) -> Optional[dict]:
     
         try:
             raw = pid_path.read_text().strip()
    -    except OSError:
    -        # File was deleted between exists() and read_text(), or permission flipped.
    +    except (OSError, UnicodeDecodeError):
    +        # File was deleted between exists() and read_text(), permission
    +        # flipped, or it holds non-UTF-8 / binary garbage.
             return None
         if not raw:
             return None
    ```
  - `tests/gateway/test_status.py`

    ```diff
    @@ -1036,3 +1036,28 @@ def test_ps_fallback_used_when_proc_returns_empty(self, monkeypatch):
             )
             result = status._read_process_cmdline(12345)
             assert "hermes_cli/main.py" in result
    +
    +
    +class TestCorruptStatusFiles:
    +    """A status / pid file holding non-UTF-8 (binary) bytes must read as
    +    None, not crash the gateway status path with UnicodeDecodeError."""
    +
    +    def test_read_json_file_returns_none_on_binary_garbage(self, tmp_path):
    +        p = tmp_path / "runtime.json"
    +        p.write_bytes(b"\xff\xfe\x00\x80not utf-8\x81")
    +        assert status._read_json_file(p) is None
    +
    +    def test_read_json_file_still_parses_valid_json(self, tmp_path):
    +        p = tmp_path / "runtime.json"
    +        p.write_text(json.dumps({"pid": 7}), encoding="utf-8")
    +        assert status._read_json_file(p) == {"pid": 7}
    +
    +    def test_read_pid_record_returns_none_on_binary_garbage(self, tmp_path):
    +        p = tmp_path / "gateway.pid"
    +        p.write_bytes(b"\xff\xfe\x00\x80\x81")
    +        assert status._read_pid_record(p) is None
    +
    +    def test_read_pid_record_still_parses_bare_pid(self, tmp_path):
    +        p = tmp_path / "gateway.pid"
    +        p.write_text("4242", encoding="utf-8")
    +        assert status._read_pid_record(p) == {"pid": 4242}
    ```
- **#28600: fix(gateway): recover from corrupt runtime state (#28579)**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#28599`
- **#28607: fix(gateway): catch UnicodeDecodeError in _read_json_file for corrupted state files (#28579)**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#39488`, `#39488`
- **#38313: fix(gateway): catch UnicodeDecodeError in _read_json_file**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#39488`

## Fix status

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