From 55ba02befbb976d2383726f1a44591c8325613f9 Mon Sep 17 00:00:00 2001 From: ayushere <44045943+ayushere@users.noreply.github.com> Date: Wed, 13 May 2026 23:17:14 -0700 Subject: [PATCH] fix(background-review): silence memory provider teardown output leak MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Background review fork redirected stdout/stderr around run_conversation() so its iteration messages stay silent. But the memory-provider teardown (shutdown_memory_provider() and review_agent.close()) fired in the outer finally block AFTER the redirect_stdout context exited — so provider teardown prints (Honcho disconnect, Hindsight sync, etc.) leaked into the parent terminal at end of every turn. Moves the teardown inside the redirect_stdout scope on the success path (and nulls review_agent so the finally safety-net skips double-shutdown). The finally block is rewritten as an exception-path safety net that re-opens a devnull redirect, since the original 'with' context has already exited by the time finally runs. Salvage of #25342 by @ayushere (manually re-applied + merged conflict with current main's set_thread_tool_whitelist wiring). --- run_agent.py | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/run_agent.py b/run_agent.py index ac9473a9691..d995c607de6 100644 --- a/run_agent.py +++ b/run_agent.py @@ -4373,6 +4373,20 @@ class AIAgent: finally: clear_thread_tool_whitelist() + # Tear down memory providers while stdout is still + # redirected so background thread teardown (Honcho flush, + # Hindsight sync, etc.) stays silent. The finally block + # below is a safety net for the exception path. + try: + review_agent.shutdown_memory_provider() + except Exception: + pass + try: + review_agent.close() + except Exception: + pass + review_agent = None + # Scan the review agent's messages for successful tool actions # and surface a compact summary to the user. Tool messages # already present in messages_snapshot must be skipped, since @@ -4402,21 +4416,24 @@ class AIAgent: logger.warning("Background memory/skill review failed: %s", e) self._emit_auxiliary_failure("background review", e) finally: - # Background review agents can initialize memory providers - # (for example Hindsight) that own their own network clients. - # Explicitly stop those providers before closing the agent so - # their aiohttp sessions do not leak until GC/process exit. - # Then close all remaining resources (httpx client, - # subprocesses, etc.) so GC doesn't try to clean them up on a - # dead asyncio event loop (which produces "Event loop is - # closed" errors). + # Safety-net cleanup for the exception path. Normal + # completion already shut down inside redirect_stdout above. + # Re-open devnull here so any teardown output (Honcho flush, + # Hindsight sync, background thread joins) stays silent even + # on the exception path where redirect_stdout already exited. if review_agent is not None: try: - review_agent.shutdown_memory_provider() - except Exception: - pass - try: - review_agent.close() + with open(os.devnull, "w", encoding="utf-8") as _fn, \ + contextlib.redirect_stdout(_fn), \ + contextlib.redirect_stderr(_fn): + try: + review_agent.shutdown_memory_provider() + except Exception: + pass + try: + review_agent.close() + except Exception: + pass except Exception: pass # Clear the approval callback on this bg-review thread so a