Capture sidecar stderr to sidecar.log for crash debugging
All checks were successful
Release / Run Tests (push) Successful in 15s
Tests / Python Backend Tests (push) Successful in 7s
Tests / Frontend Tests (push) Successful in 10s
Tests / Rust Sidecar Tests (push) Successful in 2m30s
Release / Bump version and tag (push) Successful in 12s

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) <noreply@anthropic.com>
This commit is contained in:
Developer
2026-04-07 08:41:40 -07:00
parent 3b204be37e
commit 47724f1ac0

View File

@@ -463,12 +463,64 @@ 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")?;
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.
pub fn stop(&mut self) {