From 992bdfd6e023b9e1312e435252de33ff2b2b701c Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Sun, 12 Apr 2026 22:10:06 -0700 Subject: [PATCH] fix: persist detached runner startup stderr --- ecc2/src/session/manager.rs | 60 +++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/ecc2/src/session/manager.rs b/ecc2/src/session/manager.rs index fff81a36..7be6f259 100644 --- a/ecc2/src/session/manager.rs +++ b/ecc2/src/session/manager.rs @@ -4,6 +4,7 @@ use cron::Schedule as CronSchedule; use serde::Serialize; use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt; +use std::fs::OpenOptions; use std::path::{Path, PathBuf}; use std::process::Stdio; use std::str::FromStr; @@ -2983,6 +2984,26 @@ async fn spawn_session_runner_for_program( working_dir: &Path, current_exe: &Path, ) -> Result<()> { + let stderr_log_path = background_runner_stderr_log_path(working_dir, session_id); + if let Some(parent) = stderr_log_path.parent() { + std::fs::create_dir_all(parent).with_context(|| { + format!( + "Failed to create ECC runner log directory {}", + parent.display() + ) + })?; + } + let stderr_log = OpenOptions::new() + .create(true) + .append(true) + .open(&stderr_log_path) + .with_context(|| { + format!( + "Failed to open ECC runner stderr log {}", + stderr_log_path.display() + ) + })?; + let mut command = Command::new(current_exe); command .arg("run-session") @@ -2996,7 +3017,7 @@ async fn spawn_session_runner_for_program( .arg(working_dir) .stdin(Stdio::null()) .stdout(Stdio::null()) - .stderr(Stdio::null()); + .stderr(Stdio::from(stderr_log)); configure_background_runner_command(&mut command); let child = command @@ -3009,6 +3030,21 @@ async fn spawn_session_runner_for_program( Ok(()) } +fn background_runner_stderr_log_path(working_dir: &Path, session_id: &str) -> PathBuf { + working_dir + .join(".claude") + .join("ecc2") + .join("logs") + .join(format!("{session_id}.runner-stderr.log")) +} + +#[cfg(windows)] +fn detached_creation_flags() -> u32 { + const DETACHED_PROCESS: u32 = 0x0000_0008; + const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200; + DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP +} + fn configure_background_runner_command(command: &mut Command) { #[cfg(unix)] { @@ -3030,11 +3066,7 @@ fn configure_background_runner_command(command: &mut Command) { { use std::os::windows::process::CommandExt; - const DETACHED_PROCESS: u32 = 0x0000_0008; - const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200; - command - .as_std_mut() - .creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP); + command.as_std_mut().creation_flags(detached_creation_flags()); } } @@ -5137,6 +5169,22 @@ mod tests { Ok(()) } + #[test] + fn background_runner_stderr_log_path_is_session_scoped() { + let path = + background_runner_stderr_log_path(Path::new("/tmp/ecc-repo"), "session-123"); + assert_eq!( + path, + PathBuf::from("/tmp/ecc-repo/.claude/ecc2/logs/session-123.runner-stderr.log") + ); + } + + #[cfg(windows)] + #[test] + fn detached_creation_flags_include_detach_and_process_group() { + assert_eq!(detached_creation_flags(), 0x0000_0008 | 0x0000_0200); + } + fn write_package_manager_project_files( repo_root: &Path, package_manager_field: Option<&str>,