Skip to content

Commit 4f0a01e

Browse files
committed
fix(doctor): the idle check asks the running shell's PID, not the boot
A shell that exited mid-boot logged its watcher's death, and the boot's journal kept it: with success silent, the last failure line was a dead instance's, and the live shell was graded by its predecessor. Found when a logout/login verification run failed the idle lock with the pre-logout instance's broken pipe. The journal read is scoped to the unit's main PID now; a unit with none falls back to the boot-wide read.
1 parent ef1b49f commit 4f0a01e

2 files changed

Lines changed: 45 additions & 4 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@
3131
guessed at. First warm login on the record prices the whole
3232
shell-side path at 16 ms: the blank-second cost lives in the
3333
handoff chain, not the shell.
34+
- **The doctor's idle check asks the running shell, not the boot.**
35+
The check read the whole boot's journal for an idle-watcher
36+
failure, and success is silent — so a shell that exited mid-boot
37+
(a logout teardown, a crash `Restart=always` recovered) left its
38+
watcher's death on record, and every later check that boot graded
39+
the live shell by its predecessor's corpse. Found on motherbox the
40+
same day the boot-phase logs landed: a logout/login verification
41+
run failed the idle lock with a broken pipe from the pre-logout
42+
instance. The journal read is scoped to the unit's main PID now;
43+
nothing a reader does differently — a failure it reports is the
44+
running shell's own.
3445

3546
Entries land with the change they describe; the next tag takes this section
3647
as its release notes. Say what changed and what a reader has to do

‎src/inspect.rs‎

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,13 +1924,37 @@ fn idle_watcher_failure(log: &str) -> Option<String> {
19241924
.map(str::to_string)
19251925
}
19261926

1927-
/// The idle watcher's failure line, if this boot's journal carries one.
1927+
/// The idle watcher's failure line, if the running shell's journal
1928+
/// carries one.
19281929
///
19291930
/// Outer `None` — the journal could not be read, which is not a
19301931
/// finding. Inner `Some(line)` — the watcher logged a failure. Inner
19311932
/// `None` — the journal is readable and says nothing is wrong.
1933+
///
1934+
/// The read is scoped to the unit's main PID, not to the boot: a
1935+
/// shell that exited mid-boot — a logout teardown, a crash that
1936+
/// `Restart=always` recovered — logged its watcher's death, and the
1937+
/// boot's journal keeps that line forever after. The "shell" check
1938+
/// just graded *this* process active, so *this* process is the
1939+
/// witness; a dead instance cannot testify about the live one. A unit
1940+
/// that names no PID falls back to the whole boot, where the older,
1941+
/// conservative reading applies.
19321942
fn shell_idle_broken() -> Option<Option<String>> {
1933-
let log = host_output_any(&[
1943+
let pid = host_output_any(&[
1944+
"systemctl",
1945+
"--user",
1946+
"show",
1947+
"kuma-shell.service",
1948+
"-p",
1949+
"MainPID",
1950+
"--value",
1951+
])
1952+
.ok()?;
1953+
let pid = pid.trim();
1954+
// A running unit's MainPID is its process; "0" (or empty) is
1955+
// systemd's answer for none — leave the read unscoped rather than
1956+
// asking the journal for a process that does not exist.
1957+
let mut args: Vec<String> = [
19341958
"journalctl",
19351959
"--user",
19361960
"-b",
@@ -1939,8 +1963,14 @@ fn shell_idle_broken() -> Option<Option<String>> {
19391963
"--no-pager",
19401964
"-o",
19411965
"cat",
1942-
])
1943-
.ok()?;
1966+
]
1967+
.into_iter()
1968+
.map(String::from)
1969+
.collect();
1970+
if !pid.is_empty() && pid != "0" {
1971+
args.push(format!("_PID={pid}"));
1972+
}
1973+
let log = host_output_any(&args).ok()?;
19441974
Some(idle_watcher_failure(&log))
19451975
}
19461976

0 commit comments

Comments
 (0)