{"repo":"NousResearch/hermes-agent","public_id":"28579","display_label":"Gateway JSON file UnicodeDecodeError handling","summary":"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.","subsystem":"gateway","counts":{"members":7,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":5},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":39488},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-06-05T05:05:23+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.2857142857142857},"fix_edges":[{"issue_number":28579,"pr_number":39488,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false}],"members":[{"kind":"issue","number":28579,"title":"bug: _read_json_file doesn't catch UnicodeDecodeError — corrupted gateway_state.json causes persistent warnings","url":null,"state":"closed"},{"kind":"pr","number":28599,"title":"fix: catch UnicodeDecodeError in _read_json_file for corrupted gateway_state.json","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"899ee8c23dfd029fdfd7b669ac3ac82fd8388f55","number":0},{"kind":"pr","value":"#28579","number":28579},{"kind":"pr","value":"#28600","number":28600}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":28600,"title":"fix(gateway): recover from corrupt runtime state (#28579)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#28599","number":28599}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":28607,"title":"fix(gateway): catch UnicodeDecodeError in _read_json_file for corrupted state files (#28579)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#39488","number":39488},{"kind":"pr","value":"#39488","number":39488}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":29208,"title":"fix(gateway): catch UnicodeDecodeError in _read_json_file (#28579)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"899ee8c23dfd029fdfd7b669ac3ac82fd8388f55","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":38313,"title":"fix(gateway): catch UnicodeDecodeError in _read_json_file","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#39488","number":39488}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":39488,"title":"fix(gateway): tolerate non-UTF-8 status/pid files in gateway status reads","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["gateway/status.py","tests/gateway/test_status.py"],"fix_hunks":[{"file":"gateway/status.py","hunks":["@@ -227,7 +227,10 @@ def _read_json_file(path: Path) -> Optional[dict[str, Any]]:\n         return None\n     try:\n         raw = path.read_text(encoding=\"utf-8\").strip()\n-    except OSError:\n+    except (OSError, UnicodeDecodeError):\n+        # OSError: file vanished or permission flipped between exists() and\n+        # read. UnicodeDecodeError: file holds non-UTF-8 / binary garbage\n+        # (a truncated or clobbered status file). Either way it's unusable.\n         return None\n     if not raw:\n         return None","@@ -249,8 +252,9 @@ def _read_pid_record(pid_path: Optional[Path] = None) -> Optional[dict]:\n \n     try:\n         raw = pid_path.read_text().strip()\n-    except OSError:\n-        # File was deleted between exists() and read_text(), or permission flipped.\n+    except (OSError, UnicodeDecodeError):\n+        # File was deleted between exists() and read_text(), permission\n+        # flipped, or it holds non-UTF-8 / binary garbage.\n         return None\n     if not raw:\n         return None"]},{"file":"tests/gateway/test_status.py","hunks":["@@ -1036,3 +1036,28 @@ def test_ps_fallback_used_when_proc_returns_empty(self, monkeypatch):\n         )\n         result = status._read_process_cmdline(12345)\n         assert \"hermes_cli/main.py\" in result\n+\n+\n+class TestCorruptStatusFiles:\n+    \"\"\"A status / pid file holding non-UTF-8 (binary) bytes must read as\n+    None, not crash the gateway status path with UnicodeDecodeError.\"\"\"\n+\n+    def test_read_json_file_returns_none_on_binary_garbage(self, tmp_path):\n+        p = tmp_path / \"runtime.json\"\n+        p.write_bytes(b\"\\xff\\xfe\\x00\\x80not utf-8\\x81\")\n+        assert status._read_json_file(p) is None\n+\n+    def test_read_json_file_still_parses_valid_json(self, tmp_path):\n+        p = tmp_path / \"runtime.json\"\n+        p.write_text(json.dumps({\"pid\": 7}), encoding=\"utf-8\")\n+        assert status._read_json_file(p) == {\"pid\": 7}\n+\n+    def test_read_pid_record_returns_none_on_binary_garbage(self, tmp_path):\n+        p = tmp_path / \"gateway.pid\"\n+        p.write_bytes(b\"\\xff\\xfe\\x00\\x80\\x81\")\n+        assert status._read_pid_record(p) is None\n+\n+    def test_read_pid_record_still_parses_bare_pid(self, tmp_path):\n+        p = tmp_path / \"gateway.pid\"\n+        p.write_text(\"4242\", encoding=\"utf-8\")\n+        assert status._read_pid_record(p) == {\"pid\": 4242}\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}