feat: implement atomic log saving to prevent corruption

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)
This commit is contained in:
AndyMik90
2025-12-18 23:56:20 +01:00
parent c957eaa3a1
commit da5e26b923
2 changed files with 26 additions and 3 deletions
@@ -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;
}
+18 -3
View File
@@ -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)