From da5e26b9233ed824ff2f19c1b12e05d6e87fb343 Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Thu, 18 Dec 2025 23:56:20 +0100 Subject: [PATCH] feat: implement atomic log saving to prevent corruption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhance log storage functionality by saving logs to a temporary file first, followed by an atomic rename to the final log file. This change mitigates the risk of log corruption during concurrent reads, particularly when the UI accesses the log file mid-write. Additionally, update the log loading mechanism to return cached logs if the file is detected as corrupted. Files updated: - auto-claude/task_logger/storage.py: Implement atomic log saving - auto-claude-ui/src/main/task-log-service.ts: Handle potential log file corruption by returning cached logs 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- auto-claude-ui/src/main/task-log-service.ts | 8 ++++++++ auto-claude/task_logger/storage.py | 21 ++++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/auto-claude-ui/src/main/task-log-service.ts b/auto-claude-ui/src/main/task-log-service.ts index 8cd46a63..7fa0de77 100644 --- a/auto-claude-ui/src/main/task-log-service.ts +++ b/auto-claude-ui/src/main/task-log-service.ts @@ -32,6 +32,7 @@ export class TaskLogService extends EventEmitter { /** * Load task logs from a single spec directory + * Returns cached logs if the file is corrupted (e.g., mid-write by Python backend) */ loadLogsFromPath(specDir: string): TaskLogs | null { const logFile = path.join(specDir, 'task_logs.json'); @@ -45,6 +46,13 @@ export class TaskLogService extends EventEmitter { const logs = JSON.parse(content) as TaskLogs; return logs; } catch (error) { + // JSON parse error - file may be mid-write, return cached version if available + const cached = this.logCache.get(specDir); + if (cached) { + // Silently return cached version - this is expected during concurrent access + return cached; + } + // Only log if we have no cached fallback console.error(`[TaskLogService] Failed to load logs from ${logFile}:`, error); return null; } diff --git a/auto-claude/task_logger/storage.py b/auto-claude/task_logger/storage.py index ba2605e9..6e50e89d 100644 --- a/auto-claude/task_logger/storage.py +++ b/auto-claude/task_logger/storage.py @@ -3,7 +3,9 @@ Storage functionality for task logs. """ import json +import os import sys +import tempfile from datetime import datetime, timezone from pathlib import Path @@ -65,12 +67,25 @@ class LogStorage: } def save(self) -> None: - """Save logs to file.""" + """Save logs to file atomically to prevent corruption from concurrent reads.""" self._data["updated_at"] = self._timestamp() try: self.spec_dir.mkdir(parents=True, exist_ok=True) - with open(self.log_file, "w", encoding="utf-8") as f: - json.dump(self._data, f, indent=2, ensure_ascii=False) + # Write to temp file first, then atomic rename to prevent corruption + # when the UI reads mid-write + fd, tmp_path = tempfile.mkstemp( + dir=self.spec_dir, prefix=".task_logs_", suffix=".tmp" + ) + try: + with os.fdopen(fd, "w", encoding="utf-8") as f: + json.dump(self._data, f, indent=2, ensure_ascii=False) + # Atomic rename (on POSIX systems, rename is atomic) + os.replace(tmp_path, self.log_file) + except Exception: + # Clean up temp file on failure + if os.path.exists(tmp_path): + os.unlink(tmp_path) + raise except OSError as e: print(f"Warning: Failed to save task logs: {e}", file=sys.stderr)