From e4540e6ea54e4d966456b5a3ab440c842a4e57b6 Mon Sep 17 00:00:00 2001 From: Thales <> Date: Thu, 16 Jul 2026 23:21:04 +0100 Subject: [PATCH] fix(desktop): rotate backend.log instead of truncating it on launch fs::File::create zeroed the previous session's log on every launch. The natural response to a crashed session is "restart and retry" -- which destroyed exactly the evidence needed to diagnose the crash (the Mac Mini "audio processing failed" report had nothing to paste because of this). backend.log now rotates to backend.log.1 / backend.log.2 (oldest dropped) before the new session opens it. All renames are best-effort: a locked file on Windows must never block launch -- worst case we append to the old file, which still beats truncating it. The oldest generation is removed first because Windows fs::rename fails when the destination exists. Closes #278 --- desktop/src-tauri/src/main.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/desktop/src-tauri/src/main.rs b/desktop/src-tauri/src/main.rs index 294600dc..76d67782 100644 --- a/desktop/src-tauri/src/main.rs +++ b/desktop/src-tauri/src/main.rs @@ -1068,13 +1068,34 @@ fn patch_pyvenv_cfg(python: &Path) { let _ = fs::write(&cfg_path, patched); } +/// Rotate backend.log -> backend.log.1 -> backend.log.2, dropping the oldest. +/// +/// The log used to be truncated on every launch (#278), which destroyed the +/// evidence exactly when it was needed: the natural response to a crashed +/// session is "restart and retry", and the restart wiped the previous +/// session's log. All renames are best-effort -- a locked file on Windows +/// must never block launch; worst case we append to the old file, which +/// still beats truncating it. +fn rotate_log(log_path: &Path, keep: usize) { + if keep < 2 { + return; // nothing to rotate into + } + let numbered = |i: usize| log_path.with_extension(format!("log.{i}")); + // Windows fs::rename fails when the destination exists, so drop the + // oldest generation first, then shift the rest up. + let _ = fs::remove_file(numbered(keep - 1)); + for i in (1..keep - 1).rev() { + let _ = fs::rename(numbered(i), numbered(i + 1)); + } + let _ = fs::rename(log_path, numbered(1)); +} + fn prepare_backend_stdio(log_path: &Path) -> Result<(Stdio, Stdio), String> { if let Some(parent) = log_path.parent() { fs::create_dir_all(parent) .map_err(|e| format!("failed to create backend log directory: {e}"))?; } - fs::File::create(log_path) - .map_err(|e| format!("failed to create backend log {}: {e}", log_path.display()))?; + rotate_log(log_path, 3); let stdout = fs::OpenOptions::new() .create(true) .append(true)