From f27b971565ad29764aaf8bb99e304f7e6605a29a Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 7 Mar 2026 12:44:25 -0500 Subject: [PATCH] fix(logs): Implement ultra-robust path resolution and retry logic in LogPruner --- src/log_pruner.py | 52 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/src/log_pruner.py b/src/log_pruner.py index bb9f1d4..db265ef 100644 --- a/src/log_pruner.py +++ b/src/log_pruner.py @@ -1,6 +1,7 @@ import os import shutil import sys +import time from datetime import datetime, timedelta from src.log_registry import LogRegistry @@ -49,15 +50,31 @@ class LogPruner: if not session_path: continue - # Robust path resolution - if os.path.isabs(session_path): - resolved_path = session_path - else: - # Always resolve relative to project root - resolved_path = os.path.abspath(os.path.join(project_root, session_path)) + # RESOLUTION STRATEGY: + # 1. Try as-is (absolute or relative to project root) + # 2. Try as a sub-directory of self.logs_dir (e.g. logs/sessions/session_id) + # 3. Try relative to parent of logs_dir if it starts with 'logs/' - if not os.path.isdir(resolved_path): - sys.stderr.write(f"[LogPruner] Skipping {session_id}: directory not found at {resolved_path}\n") + candidates = [] + if os.path.isabs(session_path): + candidates.append(session_path) + else: + candidates.append(os.path.abspath(os.path.join(project_root, session_path))) + candidates.append(os.path.abspath(os.path.join(self.logs_dir, session_id))) + candidates.append(os.path.abspath(os.path.join(self.logs_dir, os.path.basename(session_path)))) + + resolved_path = None + for cand in candidates: + if os.path.isdir(cand): + resolved_path = cand + break + + if not resolved_path: + # If we can't find it, we still remove it from the registry if it's "empty" + # so it stops cluttering the UI. + sys.stderr.write(f"[LogPruner] Could not find directory for {session_id} in candidates. Removing registry entry.\n") + if session_id in self.log_registry.data: + del self.log_registry.data[session_id] continue # Calculate total size of files in the directory @@ -71,11 +88,26 @@ class LogPruner: continue # Prune if the total size is less than threshold - # (The user requested always removing empty logs, get_old_non_whitelisted_sessions already handles the 'empty' check) if total_size < (min_size_kb * 1024) or total_size == 0: try: sys.stderr.write(f"[LogPruner] Removing {session_id} at {resolved_path} (Size: {total_size} bytes)\n") - shutil.rmtree(resolved_path) + + # Windows specific: sometimes files are locked. + # We try a few times with small delays. + def remove_readonly(func, path, excinfo): + os.chmod(path, 0o777) + func(path) + + for attempt in range(3): + try: + shutil.rmtree(resolved_path, onerror=remove_readonly) + break + except OSError: + if attempt < 2: + time.sleep(0.1) + else: + raise + # Also remove from registry to keep it in sync if session_id in self.log_registry.data: del self.log_registry.data[session_id]