From 6068363311b861ad0bb411bfffe5958bf8b6d142 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Fri, 15 May 2026 15:01:09 -0700 Subject: [PATCH] fix(delegate): guard heartbeat join against unstarted thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pairs with the prior commit (start() now inside the try block). If threading.Thread.start() itself raises (OS thread exhaustion under heavy delegation fanout), the finally would call .join() on a never-started thread, which raises RuntimeError("cannot join thread before it is started") — trading one rare bug for another. Thread.ident is None until start() succeeds, so gate the join on it. --- tools/delegate_tool.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/delegate_tool.py b/tools/delegate_tool.py index 2cdce9cae64..f3a037c4341 100644 --- a/tools/delegate_tool.py +++ b/tools/delegate_tool.py @@ -1836,9 +1836,13 @@ def _run_single_child( finally: # Stop the heartbeat thread so it doesn't keep touching parent activity - # after the child has finished (or failed). + # after the child has finished (or failed). Guard the join: .start() + # now lives inside the try block, so if it raised (OS thread + # exhaustion) the thread was never started and Thread.join() would + # raise RuntimeError. ident is None until start() succeeds. _heartbeat_stop.set() - _heartbeat_thread.join(timeout=5) + if _heartbeat_thread.ident is not None: + _heartbeat_thread.join(timeout=5) # Drop the TUI-facing registry entry. Safe to call even if the # child was never registered (e.g. ID missing on test doubles).