From f295b17d929b5d156d4d7c1cfb00b814b10078a0 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sun, 12 Apr 2026 12:38:55 -0700 Subject: [PATCH] fix: make agent_thread daemon to prevent orphan CLI processes on tab close (#8557) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a user closes a terminal tab, SIGHUP exits the main thread but the non-daemon agent_thread kept the entire Python process alive — stuck in the API call loop with no interrupt signal. Over many conversations, these orphan processes accumulate and cause massive swap usage (reported: 77GB on a 32GB M1 Pro). Changes: - Make agent_thread daemon=True so the process exits when the main thread finishes its cleanup. Under normal operation this changes nothing — the main thread already waits on agent_thread.is_alive(). - Interrupt the agent in the finally/exit path so the daemon thread stops making API calls promptly rather than being killed mid-flight. --- cli.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/cli.py b/cli.py index 761542454..fdf625481 100644 --- a/cli.py +++ b/cli.py @@ -7617,8 +7617,10 @@ class HermesCLI: "error": _summary, } - # Start agent in background thread - agent_thread = threading.Thread(target=run_agent) + # Start agent in background thread (daemon so it cannot keep the + # process alive when the user closes the terminal tab — SIGHUP + # exits the main thread and daemon threads are reaped automatically). + agent_thread = threading.Thread(target=run_agent, daemon=True) agent_thread.start() # Monitor the dedicated interrupt queue while the agent runs. @@ -9626,6 +9628,15 @@ class HermesCLI: raise finally: self._should_exit = True + # Interrupt the agent immediately so its daemon thread stops making + # API calls and exits promptly (agent_thread is daemon, so the + # process will exit once the main thread finishes, but interrupting + # avoids wasted API calls and lets run_conversation clean up). + if self.agent and getattr(self, '_agent_running', False): + try: + self.agent.interrupt() + except Exception: + pass # Flush memories before exit (only for substantial conversations) if self.agent and self.conversation_history: try: