Skip to main content

devflow_core/
agent.rs

1//! Agent process helpers.
2//!
3//! All agents run in non-interactive mode (`claude -p`, `codex exec`) under a
4//! detached monitor that owns the process and its capture files (see
5//! [`crate::monitor`]). The old synchronous launch/capture path
6//! (`launch_agent` + `capture_agent_output`) was removed in 14b once
7//! `sequentagent` moved behind monitor-owned execution — the monitor is now
8//! the single way an agent process is spawned.
9
10/// Check whether a process with the given PID is still running.
11///
12/// The PID typically comes from parsing an on-disk file, so hostile or
13/// corrupted values must be rejected, not reinterpreted: `kill(0, sig)`
14/// signals the caller's own process group (a "0" PID file would read as
15/// permanently alive), and a value above `i32::MAX` would wrap negative
16/// through an `as libc::pid_t` cast — `kill(-1, 0)` probes every process
17/// the caller may signal and virtually always succeeds.
18pub fn agent_running(pid: u32) -> bool {
19    // kill(pid, 0) is the standard POSIX way to check process existence
20    // without sending an actual signal.
21    let Ok(pid) = libc::pid_t::try_from(pid) else {
22        return false;
23    };
24    pid > 0 && unsafe { libc::kill(pid, 0) == 0 }
25}
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30
31    #[test]
32    fn agent_running_detects_self() {
33        // The current process is, by definition, running.
34        assert!(agent_running(std::process::id()));
35    }
36
37    #[test]
38    fn agent_running_false_for_dead_pid() {
39        // A PID near the top of the range is essentially never live.
40        assert!(!agent_running(0x7FFF_FFFE));
41    }
42
43    #[test]
44    fn agent_running_rejects_corrupt_pid_values() {
45        // "0" from a truncated PID file: kill(0, 0) would signal our own
46        // process group and report alive.
47        assert!(!agent_running(0));
48        // Above i32::MAX: `as libc::pid_t` would wrap to -1, and
49        // kill(-1, 0) probes every signalable process — almost always "alive".
50        assert!(!agent_running(u32::MAX));
51        assert!(!agent_running(i32::MAX as u32 + 1));
52    }
53}