{"repo":"NousResearch/hermes-agent","public_id":"21123","display_label":"TUI freeze caused by blocking completion RPCs","summary":"A bug where typing '@' in the TUI triggers a completion RPC that blocks the stdio pipe and freezes the interface. The fixes route or skip these RPCs to prevent the freeze.","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":53895,"merged_at":"2026-06-28T02:06:01+00:00"},"resolution":{"state":"resolved","by":"pr-53895"},"freshness":{"state":"verified","last_verified_at":"2026-06-28T02:06:01+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":21123,"pr_number":53895,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-06-28T02:06:01+00:00"}],"members":[{"kind":"issue","number":21123,"title":"TUI freezes when typing @ character — complete.path RPC timeout blocks stdio pipe","url":null,"state":"closed"},{"kind":"pr","number":21142,"title":"fix: skip bare @ completion RPC in TUI","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#21123","number":21123},{"kind":"pr","value":"#53895","number":53895}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":53895,"title":"fix(tui): route completion RPCs to the pool so they can't freeze the TUI","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tests/tui_gateway/test_protocol.py","tui_gateway/server.py"],"fix_hunks":[{"file":"tests/tui_gateway/test_protocol.py","hunks":["@@ -1736,3 +1736,37 @@ def test_dispatch_unknown_long_method_still_goes_inline(server):\n     resp = server.dispatch({\"id\": \"r4\", \"method\": \"some.method\", \"params\": {}})\n \n     assert resp[\"result\"] == {\"ok\": True}\n+\n+\n[redacted:email](\"completion_method\", [\"complete.path\", \"complete.slash\"])\n+def test_completion_handlers_are_pool_routed(completion_method, server):\n+    \"\"\"complete.path/complete.slash must run on the pool, never the reader thread.\n+\n+    Regression for #21123: completion ran inline, so a slow git ls-files /\n+    skill-scan blocked prompt.submit and froze the TUI for the 120s RPC timeout.\n+    \"\"\"\n+    assert completion_method in server._LONG_HANDLERS\n+\n+\n[redacted:email](\"completion_method\", [\"complete.path\", \"complete.slash\"])\n+def test_slow_completion_does_not_block_fast_handler(completion_method, server):\n+    \"\"\"A slow completion RPC must not block a concurrent fast handler (#21123).\"\"\"\n+    released = threading.Event()\n+\n+    def slow_completion(rid, params):\n+        released.wait(timeout=5)\n+        return server._ok(rid, {\"items\": []})\n+\n+    server._methods[completion_method] = slow_completion\n+    server._methods[\"fast.ping\"] = lambda rid, params: server._ok(rid, {\"pong\": True})\n+\n+    t0 = time.monotonic()\n+    assert server.dispatch({\"id\": \"slow\", \"method\": completion_method, \"params\": {}}) is None\n+\n+    fast_resp = server.dispatch({\"id\": \"fast\", \"method\": \"fast.ping\", \"params\": {}})\n+    fast_elapsed = time.monotonic() - t0\n+\n+    assert fast_resp[\"result\"] == {\"pong\": True}\n+    assert fast_elapsed < 0.5, f\"fast handler blocked for {fast_elapsed:.2f}s behind {completion_method}\"\n+\n+    released.set()"]},{"file":"tui_gateway/server.py","hunks":["@@ -178,6 +178,16 @@ def _thread_panic_hook(args):\n         \"billing.step_up\",\n         \"browser.manage\",\n         \"cli.exec\",\n+        # Completion RPCs run inline on the reader thread by default, but both\n+        # can block it for seconds: complete.path spawns `git ls-files` and\n+        # fuzzy-ranks the whole repo (slow on large repos / WSL2 mounts), and\n+        # complete.slash does first-call prompt_toolkit imports + a skill-dir\n+        # scan. While either runs inline, prompt.submit / session.interrupt sit\n+        # unread in the stdin pipe — the TUI appears frozen until the 120s RPC\n+        # timeout fires (#21123). Routing them to the pool keeps the fast path\n+        # responsive; completion is read-only and write_json is lock-guarded.\n+        \"complete.path\",\n+        \"complete.slash\",\n         \"llm.oneshot\",\n         # Pet RPCs hit the network (manifest fetch / spritesheet download) or do\n         # per-frame PNG decode/encode (pet.cells): inline they serialize on the\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}