{"repo":"NousResearch/hermes-agent","public_id":"16803","display_label":"CLI process loop freeze and input unresponsiveness","summary":"These members address a bug where the CLI process loop becomes unresponsive, ignoring user input and resisting interrupts like Ctrl-C. Fixes target the process loop lifecycle, MCP reload join behavior, and related flag handling.","subsystem":"hermes_cli","counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":55964,"merged_at":"2026-07-01T00:20:50+00:00"},"resolution":{"state":"resolved","by":"pr-55964"},"freshness":{"state":"verified","last_verified_at":"2026-07-01T00:20:50+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":16803,"pr_number":55964,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-07-01T00:20:50+00:00"}],"members":[{"kind":"issue","number":16803,"title":"[Bug]: hermes-cli enter a dummy status, it ignore any input, and ctl-c, only can kill it","url":null,"state":"closed"},{"kind":"pr","number":16931,"title":"fix: make process_loop never die, preventing TUI input black hole","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"972aa33d3","number":0},{"kind":"pr","value":"#55964","number":55964}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":55964,"title":"fix(cli): prevent process_loop freeze from MCP reload join and voice flag leak (#16803)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["cli.py"],"fix_hunks":[{"file":"cli.py","hunks":["@@ -10107,9 +10107,11 @@ def _check_config_mcp_changes(self) -> None:\n             target=self._reload_mcp, daemon=True\n         )\n         _reload_thread.start()\n-        _reload_thread.join(timeout=30)\n-        if _reload_thread.is_alive():\n-            print(\"  ⚠️  MCP reload timed out (30s). Some servers may not have reconnected.\")\n+        # Do NOT join here — process_loop calls this from its idle branch, so a\n+        # blocking join would freeze input consumption for up to 30s (and a hung\n+        # MCP server could block far longer). The reload runs purely in the\n+        # background daemon thread, which reports its own progress/completion\n+        # status via print() inside _reload_mcp().\n \n     # Inline-skip tokens that bypass the destructive-slash confirmation modal.\n     # A general escape hatch for non-interactive use (scripting/automation) and","@@ -10737,8 +10739,16 @@ def _voice_start_recording(self):\n         except Exception:\n             pass\n \n+        # Recorder creation can fail (no input device, PortAudio init error).\n+        # Reset the flag on failure or _voice_recording stays True forever and\n+        # every future voice start is silently skipped by the guard above.\n         if self._voice_recorder is None:\n-            self._voice_recorder = create_audio_recorder()\n+            try:\n+                self._voice_recorder = create_audio_recorder()\n+            except Exception:\n+                with self._voice_lock:\n+                    self._voice_recording = False\n+                raise\n \n         # Apply config-driven silence params (numeric-guarded so YAML\n         # scalar corruption doesn't break recording start-up).\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}