blankpad has had no screen lock for 8 days; the lid suspends it unlocked #48

Merged
dank merged 1 commits from fix/47-lock-watcher-resilience into main 2026-10-20 17:25:12 -07:00
Owner

Your workstation has had no screen lock for eight days, and the lid suspends it unlocked

Found while verifying #46 — I was checking unit states and this one was lying: enabled, listed
in default.target.wants/, and inactive (dead) since Mon 2026-10-12 16:22:23, eight days.

Duration: 2d 21h 57min   Main PID: 3181 (code=exited, status=0/SUCCESS)
Result=success   NRestarts=0   ExecMainStartTimestamp=Fri 2026-10-09 18:24:49

The unit said Restart=on-failure. swayidle -w exits 0 on SIGTERM, so a clean exit is not a
failure, systemd never restarted it, and nothing else locks: pgrep -c -u blankpad 'swayidle|swaylock|hyprlock|gtklock' = 0, no locker in either autostart.kdl.

The sharp part is suspend. This watcher also owned before-sleep '/usr/bin/noctalia msg session lock', and logind says HandleLidSwitch=suspend with HandleLidSwitchExternalPower="" falling
back to it. So closing the lid suspended blankpad without locking it, on battery and on
mains, for a week. The hook that would have locked it was inside the dead unit.

What triggered the exit is unattributed — the user journal has nothing in that window — but the fix
does not need to know, which is the entire lesson.

Change

setting why
Restart=always a supervisor must not assume it can only die badly
StartLimitIntervalSec=0 with always, a fast loop must never wedge it into failed; a trying lock beats a stopped one
PartOf=graphical-session.target wanted by default.target, so a session restart brought the target back without it; now they come back together

Verified with systemd-analyze --user verify (clean), bats tests/ 71/71, kb-lint 0,
kb-coverage 0. The law recorded in omp-docs/tools/dotfiles-deploy.md states how to prove it
behaviourally — start it, kill -TERM the main PID, watch NRestarts increment and MainPID
change — which is the October event survived, rather than a grep over a config file.

Correction after merge — see PR #49

Two claims above were fixed by measurement: the trigger is attributed (niri and noctalia started Mon Oct 12 16:22:31 2026, eight seconds around swayidle's 16:22:23 exit), and the lid did not suspend the machine unlocked — systemd-inhibit --list shows noctalia … sleep "Lock before sleep" delay, so suspend was covered by the shell all along. What was dead for eight days is the idle lock and after-resume. The fix is unchanged.

Not done, deliberately

The unit is not started by this PR. Starting it arms a 5-minute idle lock on a machine that has
been without one for a week; that is your call, not a side effect of a merge. Say the word and I
start it and run the SIGTERM proof.

Also left open on purpose, and named in the docs: deploy.sh only ever enables. Nothing on any
box re-checks that a unit the system depends on is still running, which is how a security posture
lapses for eight days with a green is-enabled the whole time. A liveness check for this tier
belongs in the heartbeat.


cwd: /home/blank/.omp/wt/wt-20261019-125509-7c8a99d · session: 01a155bb-6474-7366-be9d-629f74af0e05
Build worktree: ~/.omp/wt/wt-lock-unit off origin/main. Refs #47

# Your workstation has had no screen lock for eight days, and the lid suspends it unlocked Found while verifying #46 — I was checking unit states and this one was lying: `enabled`, listed in `default.target.wants/`, and `inactive (dead)` since **Mon 2026-10-12 16:22:23**, eight days. Duration: 2d 21h 57min Main PID: 3181 (code=exited, status=0/SUCCESS) Result=success NRestarts=0 ExecMainStartTimestamp=Fri 2026-10-09 18:24:49 The unit said `Restart=on-failure`. `swayidle -w` exits **0** on SIGTERM, so a clean exit is not a failure, systemd never restarted it, and nothing else locks: `pgrep -c -u blankpad 'swayidle|swaylock|hyprlock|gtklock'` = 0, no locker in either `autostart.kdl`. The sharp part is suspend. This watcher also owned `before-sleep '/usr/bin/noctalia msg session lock'`, and logind says `HandleLidSwitch=suspend` with `HandleLidSwitchExternalPower=""` falling back to it. So closing the lid **suspended blankpad without locking it**, on battery and on mains, for a week. The hook that would have locked it was inside the dead unit. What triggered the exit is unattributed — the user journal has nothing in that window — but the fix does not need to know, which is the entire lesson. ## Change |setting|why| |---|---| |`Restart=always`|a supervisor must not assume it can only die badly| |`StartLimitIntervalSec=0`|with `always`, a fast loop must never wedge it into `failed`; a trying lock beats a stopped one| |`PartOf=graphical-session.target`|wanted by `default.target`, so a session restart brought the target back **without** it; now they come back together| Verified with `systemd-analyze --user verify` (clean), `bats tests/` 71/71, `kb-lint` 0, `kb-coverage` 0. The law recorded in `omp-docs/tools/dotfiles-deploy.md` states how to prove it behaviourally — start it, `kill -TERM` the main PID, watch `NRestarts` increment and `MainPID` change — which is the October event survived, rather than a grep over a config file. ## Correction after merge — see PR #49 Two claims above were fixed by measurement: the trigger is attributed (niri and noctalia started `Mon Oct 12 16:22:31 2026`, eight seconds around swayidle's `16:22:23` exit), and **the lid did not suspend the machine unlocked** — `systemd-inhibit --list` shows `noctalia … sleep "Lock before sleep" delay`, so suspend was covered by the shell all along. What was dead for eight days is the idle lock and `after-resume`. The fix is unchanged. ## Not done, deliberately The unit is **not started** by this PR. Starting it arms a 5-minute idle lock on a machine that has been without one for a week; that is your call, not a side effect of a merge. Say the word and I start it and run the SIGTERM proof. Also left open on purpose, and named in the docs: `deploy.sh` only ever *enables*. Nothing on any box re-checks that a unit the system depends on is still running, which is how a security posture lapses for eight days with a green `is-enabled` the whole time. A liveness check for this tier belongs in the heartbeat. --- cwd: /home/blank/.omp/wt/wt-20261019-125509-7c8a99d · session: 01a155bb-6474-7366-be9d-629f74af0e05 Build worktree: `~/.omp/wt/wt-lock-unit` off `origin/main`. Refs #47
dank added 1 commit 2026-10-20 17:24:33 -07:00
`swayidle-lock.service` was `enabled`, in `default.target.wants/`, and **dead for eight days**:

    Active: inactive (dead) since Mon 2026-10-12 16:22:23 PDT; 1 week 1 day ago
    Duration: 2d 21h 57min   Main PID: 3181 (code=exited, status=0/SUCCESS)
    Result=success  NRestarts=0

`Restart=on-failure` cannot see a clean exit, and `swayidle -w` exits 0 on SIGTERM. So the
watcher that owns both the 5-minute idle lock and the `before-sleep` lock stopped existing, and
nothing on the box noticed. The consequence is not cosmetic: with `HandleLidSwitch=suspend` and
`HandleLidSwitchExternalPower=""` (which falls back to it), **closing the lid suspended the
machine unlocked**, because the hook that locks on sleep lived inside the unit that had died. No
alternative was running: `pgrep -c -u blankpad 'swayidle|swaylock|hyprlock|gtklock'` = 0, and
neither `autostart.kdl` launches a locker.

Three settings, each with a job:

- `Restart=always` — a supervisor must not assume it can only die badly.
- `StartLimitIntervalSec=0` — with `always`, a fast loop must never wedge the unit into `failed`;
  a lock that keeps trying beats a lock that stopped trying.
- `PartOf=graphical-session.target` — it is wanted by `default.target`, so a session restart
  brought the target back without it; now the watcher is restarted with the session.

What triggered the 16:22 exit is **unattributed**: the user journal has no entries in that window,
so all that can be said is that swayidle exited with status 0 — precisely what the old policy was
blind to. The fix does not depend on knowing the trigger, which is the point.

Verification is behavioural, per the law recorded with it: start the unit, `kill -TERM` the main
PID, and watch `NRestarts` increment with a new `MainPID` — the October event, survived. Not
performed here: starting it arms a 5-minute idle lock on a machine that has gone a week without
one, and that is Frank's call. `systemd-analyze --user verify` clean, `bats tests/` 71/71,
`kb-lint.sh omp-docs` 0, `kb-coverage.py` 0.

Refs #47
dank merged commit d013795922 into main 2026-10-20 17:25:12 -07:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dank/command-center#48