From df00805bee5fecda4ea4c45d9b8ced81982c8fbf Mon Sep 17 00:00:00 2001 From: "Dr.Lt.Data" Date: Sun, 22 Mar 2026 10:18:15 +0900 Subject: [PATCH] fix(core): harden try_rmtree with retry and rename for Windows On Windows, shutil.rmtree fails when files are locked by antivirus or git handles. The current fallback (reserve_script for lazy delete) is useless in cm-cli where there is no restart cycle, causing reinstall to fail with "Already exists". 3-tier deletion strategy: 1. Retry rmtree 3x with 1s delay (handles transient locks) 2. Rename to .trash_* then delete (moves out of scan path) 3. Lazy delete via reserve_script (ComfyUI GUI fallback) After rename, lazy-delete targets the .trash_* path (not original), so the original path is clear for subsequent clone/install. --- comfyui_manager/glob/manager_core.py | 29 ++++++++++++++++++++++---- comfyui_manager/legacy/manager_core.py | 29 ++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/comfyui_manager/glob/manager_core.py b/comfyui_manager/glob/manager_core.py index 8b166cb3..68702a27 100644 --- a/comfyui_manager/glob/manager_core.py +++ b/comfyui_manager/glob/manager_core.py @@ -1854,11 +1854,32 @@ def reserve_script(repo_path, install_cmds): def try_rmtree(title, fullpath): + # Tier 1: retry with delay for transient Windows file locks + for attempt in range(3): + try: + shutil.rmtree(fullpath) + return + except OSError: + if attempt < 2: + time.sleep(1) + + # Tier 2: rename out of scan path so clone/install can proceed + trash = fullpath + f'.trash_{uuid.uuid4().hex[:8]}' try: - shutil.rmtree(fullpath) - except Exception as e: - logging.warning(f"[ComfyUI-Manager] An error occurred while deleting '{fullpath}', so it has been scheduled for deletion upon restart.\nEXCEPTION: {e}") - reserve_script(title, ["#LAZY-DELETE-NODEPACK", fullpath]) + os.rename(fullpath, trash) + shutil.rmtree(trash, ignore_errors=True) + if not os.path.exists(trash): + return + # Rename succeeded but delete failed — schedule trash path for lazy delete + logging.warning(f"[ComfyUI-Manager] Renamed '{fullpath}' to '{trash}' but could not delete; scheduled for restart.") + reserve_script(title, ["#LAZY-DELETE-NODEPACK", trash]) + return + except OSError: + pass + + # Tier 3: lazy delete on restart (ComfyUI GUI fallback) + logging.warning(f"[ComfyUI-Manager] An error occurred while deleting '{fullpath}', so it has been scheduled for deletion upon restart.") + reserve_script(title, ["#LAZY-DELETE-NODEPACK", fullpath]) def try_install_script(url, repo_path, install_cmd, instant_execution=False): diff --git a/comfyui_manager/legacy/manager_core.py b/comfyui_manager/legacy/manager_core.py index de6b29f3..905d3a7c 100644 --- a/comfyui_manager/legacy/manager_core.py +++ b/comfyui_manager/legacy/manager_core.py @@ -1833,11 +1833,32 @@ def reserve_script(repo_path, install_cmds): def try_rmtree(title, fullpath): + # Tier 1: retry with delay for transient Windows file locks + for attempt in range(3): + try: + shutil.rmtree(fullpath) + return + except OSError: + if attempt < 2: + time.sleep(1) + + # Tier 2: rename out of scan path so clone/install can proceed + trash = fullpath + f'.trash_{uuid.uuid4().hex[:8]}' try: - shutil.rmtree(fullpath) - except Exception as e: - logging.warning(f"[ComfyUI-Manager] An error occurred while deleting '{fullpath}', so it has been scheduled for deletion upon restart.\nEXCEPTION: {e}") - reserve_script(title, ["#LAZY-DELETE-NODEPACK", fullpath]) + os.rename(fullpath, trash) + shutil.rmtree(trash, ignore_errors=True) + if not os.path.exists(trash): + return + # Rename succeeded but delete failed — schedule trash path for lazy delete + logging.warning(f"[ComfyUI-Manager] Renamed '{fullpath}' to '{trash}' but could not delete; scheduled for restart.") + reserve_script(title, ["#LAZY-DELETE-NODEPACK", trash]) + return + except OSError: + pass + + # Tier 3: lazy delete on restart (ComfyUI GUI fallback) + logging.warning(f"[ComfyUI-Manager] An error occurred while deleting '{fullpath}', so it has been scheduled for deletion upon restart.") + reserve_script(title, ["#LAZY-DELETE-NODEPACK", fullpath]) def try_install_script(url, repo_path, install_cmd, instant_execution=False):