Correct the lock-watcher claims: session restart was the trigger; suspend was covered by noctalia #49

Merged
dank merged 1 commits from fix/47-attribution-and-inhibitor-correction into main 2026-10-20 17:55:08 -07:00
+24 -6
View File
@@ -210,12 +210,23 @@ Refs #44.
`swayidle-lock.service` was `enabled`, present in `default.target.wants/`, and **dead for eight
days**: `Result=success`, `NRestarts=0`, gone since 2026-10-12 16:22 after 2d 21h of uptime.
`Restart=on-failure` cannot see a clean exit, and `swayidle -w` exits 0 on SIGTERM — so the
watcher that owns the 5-minute idle lock *and* the `before-sleep` lock quietly stopped existing,
and nothing noticed. With it dead and logind at `HandleLidSwitch=suspend`, closing the lid
suspended the machine **unlocked**: the lock hook lived inside the thing that had died. No
alternative existed — `pgrep -c -u blankpad 'swayidle|swaylock|hyprlock|gtklock'` = 0, and neither
`autostart.kdl` launches a locker.
`Restart=on-failure` cannot see a clean exit, and `swayidle -w` exits 0 on SIGTERM. The trigger is
attributable to eight seconds: **`niri` and `noctalia` both started `Mon Oct 12 16:22:31 2026`**
(`ps -o lstart=`) while swayidle's last state change was `16:22:23` — the compositor session
restarted, the watcher went with it, and because it was wanted by `default.target` rather than tied
to the session target, nothing brought it back. The user journal keeps no entries for that window,
so the attribution rests on process ages rather than logs, which is its own lesson: diagnose a
lapse within the retention you actually have.
**The severity claim in this page's first draft was wrong, and is corrected here.** It said closing
the lid suspended the machine unlocked, because the `before-sleep` hook lived in the dead unit. But
`systemd-inhibit --list` shows `noctalia … sleep "Lock before sleep" delay` — the shell holds its
own sleep inhibitor and locks on suspend independently of swayidle, so the suspend path was covered
the whole time. What was actually dead for eight days is the **idle** lock: five minutes of
inactivity locked nothing, and `after-resume` never fired. `pgrep -c -u blankpad
'swayidle|swaylock|hyprlock|gtklock'` = 0 remains true; it simply did not mean *no lock path at
all*, because a locker can be a hook inside a running shell rather than a name on the process list.
Read the inhibitor table, not only `pgrep`.
Three settings, each with a job: `Restart=always`, because a supervisor must not assume it can
only die badly; `StartLimitIntervalSec=0`, because a fast loop must never wedge it into `failed` —
@@ -232,6 +243,13 @@ kill -TERM "$(systemctl --user show swayidle-lock.service -p MainPID --value)"
sleep 8 && systemctl --user show swayidle-lock.service -p NRestarts -p MainPID -p ActiveState
```
An idle inhibitor also disarms the timeout outright, which surprises people: while an `omp` agent
session is running, swayidle logs `Not enabling timeouts: idle inhibitor found` and arms no idle
timeout at all — `systemd-inhibit --list` shows `omp … idle "omp agent session" block`. A box with
an agent working therefore does not lock on inactivity, by harness design, while `before-sleep` and
`after-resume` keep working. Anyone asking "why did the screen never lock" should read the inhibitor
table before suspecting the unit.
The general shape: `deploy.sh` only ever *enables*. Nothing on any box re-checks that a unit the
system depends on is still running, so a security posture can lapse silently and stay lapsed for a
week. A liveness check for this tier belongs in the heartbeat, not in a human's memory. Refs #47.