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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user