use std::process::Command;
use std::thread::sleep;
use std::time::Duration;
pub fn find_claude_pid_in_tmux(
session_name: &str,
max_attempts: u8,
delay: Duration,
) -> Option<u32> {
for attempt in 0..max_attempts.max(1) {
if attempt > 0 {
sleep(delay);
}
let Some(pane_pid) = tmux_pane_pid(session_name) else {
return None;
};
if let Some(pid) = claude_child_of(pane_pid) {
return Some(pid);
}
}
None
}
fn tmux_pane_pid(session_name: &str) -> Option<u32> {
let output = Command::new("tmux")
.args(["display-message", "-t", session_name, "-p", "#{pane_pid}"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8_lossy(&output.stdout);
text.trim().parse::<u32>().ok()
}
fn claude_child_of(shell_pid: u32) -> Option<u32> {
let output = Command::new("pgrep")
.args(["-P", &shell_pid.to_string()])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let text = String::from_utf8_lossy(&output.stdout);
text.lines()
.filter_map(|line| line.trim().parse::<u32>().ok())
.find(|&pid| process_name_contains_claude(pid))
}
fn process_name_contains_claude(pid: u32) -> bool {
#[cfg(target_os = "linux")]
{
if let Ok(comm) = std::fs::read_to_string(format!("/proc/{pid}/comm")) {
return comm.to_ascii_lowercase().contains("claude");
}
}
let output = Command::new("ps")
.args(["-p", &pid.to_string(), "-o", "comm="])
.output();
match output {
Ok(out) if out.status.success() => String::from_utf8_lossy(&out.stdout)
.to_ascii_lowercase()
.contains("claude"),
_ => false,
}
}
pub fn is_process_alive(pid: u32) -> bool {
use nix::errno::Errno;
use nix::sys::signal::kill;
use nix::unistd::Pid;
let Ok(raw) = i32::try_from(pid) else {
return false;
};
if raw <= 0 {
return false;
}
match kill(Pid::from_raw(raw), None) {
Ok(()) => true,
Err(Errno::EPERM) => true,
Err(_) => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn find_claude_pid_returns_none_for_nonexistent_session() {
let pid = find_claude_pid_in_tmux(
"tmpm-definitely-not-a-real-session-xyz",
2,
Duration::from_millis(1),
);
assert_eq!(pid, None);
}
#[test]
fn is_process_alive_current_process() {
assert!(is_process_alive(std::process::id()));
}
#[test]
fn is_process_alive_dead_pid() {
assert!(!is_process_alive(u32::MAX));
}
}