1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//! OS-level process tracking for tmux-hosted `claude` sessions.
//!
//! Why: a tmux session can stay alive long after the `claude` process inside it
//! exits (the pane drops back to a shell). Tracking the real `claude` PID lets
//! the daemon detect a stopped session and mark it as such rather than reporting
//! a hollow tmux window as still active.
//! What: [`find_claude_pid_in_tmux`] resolves the `claude` PID under a tmux
//! pane's shell, and [`is_process_alive`] checks whether a recorded PID still
//! refers to a live process.
//! Test: `cargo test -p trusty-mpm-core process` covers liveness for the
//! current process, a guaranteed-dead PID, and a bogus tmux session name.
use Command;
use sleep;
use Duration;
/// Find the PID of the `claude` process running as a child of a tmux pane.
///
/// Why: trusty-mpm launches `claude` with `tmux send-keys`, so it never gets a
/// PID directly; it must be discovered after the fact by walking the pane's
/// process tree.
/// What:
/// 1. Get the pane's shell PID: `tmux display-message -t <session> -p '#{pane_pid}'`.
/// 2. Find a child process named `claude`/`claude-code` via `pgrep -P <pane_pid>`.
/// 3. Retry up to `max_attempts` times with `delay` between attempts, since
/// `claude` takes 1-3 s to start after `send-keys`.
///
/// Returns `None` if tmux is unavailable, the pane has no shell PID, or no
/// `claude` child is found within the retry budget.
/// Test: `find_claude_pid_returns_none_for_nonexistent_session`.
/// Read the shell PID of a tmux session's active pane.
///
/// Why: the `claude` process is a child of this shell; it is the root we walk
/// the process tree from.
/// What: runs `tmux display-message -t <session> -p '#{pane_pid}'` and parses
/// the single integer it prints. Returns `None` when tmux is absent or the
/// session does not exist.
/// Test: exercised via `find_claude_pid_returns_none_for_nonexistent_session`.
/// Find a direct child of `shell_pid` whose command name contains `claude`.
///
/// Why: after `send-keys "claude"`, the `claude` process is a child of the
/// pane's shell; matching by command name avoids picking up unrelated children.
/// What: runs `pgrep -P <shell_pid>` and returns the first child whose process
/// name (`/proc/<pid>/comm` on Linux, `ps -p <pid> -o comm=` on macOS) contains
/// `claude`.
/// Test: exercised via `find_claude_pid_returns_none_for_nonexistent_session`.
/// Check whether process `pid`'s command name contains `claude`.
///
/// Why: a shell may have several children; only the `claude`/`claude-code`
/// process is the one trusty-mpm is tracking.
/// What: reads `/proc/<pid>/comm` on Linux, falling back to `ps -p <pid> -o
/// comm=` (the portable path used on macOS). A case-insensitive `claude`
/// substring match accepts both `claude` and `claude-code`.
/// Test: exercised via `find_claude_pid_returns_none_for_nonexistent_session`.
/// Check whether the process at `pid` is (still) a `claude` process.
///
/// Why: the PID-file orphan-GC ([`crate::core::pid_registry`]) must verify, just
/// before SIGTERMing a recorded PID, that the process at that PID is still named
/// `claude` — the alpha-1 mitigation for the PID-reuse race (spec §13.5). A
/// recycled PID now owned by an unrelated process fails this check and is spared.
/// What: a thin public wrapper over the crate-internal `comm`/`ps` name check,
/// returning `true` only when the process's command name contains `claude`
/// (case-insensitive), matching both `claude` and `claude-code`. A dead PID or
/// any lookup failure returns `false`.
/// Test: `process_name_is_claude_for_dead_pid_is_false` (a non-existent PID is
/// never `claude`); the positive path is covered by `find_claude_pid_in_tmux`.
/// Check whether a process with the given PID is still alive.
///
/// Why: the daemon's reaper must distinguish a tmux session whose `claude`
/// process is still running from one that has dropped back to a bare shell.
/// What: uses `kill(pid, 0)` (POSIX) — a null signal that performs the
/// permission/existence check without delivering anything. Returns `true` when
/// the process exists (signal sent, or it exists but is owned by another user),
/// `false` only when no such process exists. A PID outside the positive `pid_t`
/// range is treated as dead — `kill` interprets `0` and negative values as
/// process groups, never a single process.
/// Test: `is_process_alive_current_process`, `is_process_alive_dead_pid`.