From 47724f1ac0ba714327a4f2c77a3a6503d854eda9 Mon Sep 17 00:00:00 2001 From: Developer Date: Tue, 7 Apr 2026 08:41:40 -0700 Subject: [PATCH] Capture sidecar stderr to sidecar.log for crash debugging When the sidecar process exits before sending the ready event, the error message now includes the last 10 lines of stderr. Stderr is captured in a background thread and written to sidecar.log in the app data directory. This helps diagnose why the PyInstaller sidecar fails to start (missing DLLs, import errors, permission issues, etc.). Log location: %APPDATA%\net.anhonesthost.local-transcription\sidecar.log Co-Authored-By: Claude Opus 4.6 (1M context) --- src-tauri/src/sidecar/mod.rs | 60 +++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/sidecar/mod.rs b/src-tauri/src/sidecar/mod.rs index bc4efc6..9d97ab5 100644 --- a/src-tauri/src/sidecar/mod.rs +++ b/src-tauri/src/sidecar/mod.rs @@ -463,11 +463,63 @@ impl SidecarManager { .take() .ok_or("Failed to capture sidecar stdout")?; - let port = Self::wait_for_ready(stdout)?; + // Capture stderr in a background thread so we can log it + let stderr = child + .stderr + .take() + .ok_or("Failed to capture sidecar stderr")?; - self.child = Some(child); - self.port = Some(port); - Ok(port) + let log_dir = DIRS.get().map(|d| d.data_dir.clone()); + std::thread::spawn(move || { + use std::io::BufRead; + let reader = std::io::BufReader::new(stderr); + let mut log_file = log_dir.and_then(|d| { + std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(d.join("sidecar.log")) + .ok() + }); + for line in reader.lines() { + if let Ok(line) = line { + eprintln!("[sidecar-stderr] {}", line); + if let Some(ref mut f) = log_file { + use std::io::Write; + let _ = writeln!(f, "{}", line); + } + } + } + }); + + match Self::wait_for_ready(stdout) { + Ok(port) => { + self.child = Some(child); + self.port = Some(port); + Ok(port) + } + Err(e) => { + // Kill the child if ready failed + let _ = child.kill(); + let _ = child.wait(); + + // Read the sidecar.log for context + let log_hint = DIRS + .get() + .and_then(|d| std::fs::read_to_string(d.data_dir.join("sidecar.log")).ok()) + .and_then(|s| { + let lines: Vec<&str> = s.lines().collect(); + let tail: Vec<&str> = lines.iter().rev().take(10).rev().cloned().collect(); + if tail.is_empty() { None } else { Some(tail.join("\n")) } + }) + .unwrap_or_default(); + + if log_hint.is_empty() { + Err(e) + } else { + Err(format!("{e}\n\nSidecar stderr (last 10 lines):\n{log_hint}")) + } + } + } } /// Stop the sidecar process if running.