`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
29 lines
1.3 KiB
Desktop File
29 lines
1.3 KiB
Desktop File
[Unit]
|
|
Description=swayidle idle lock (locks via noctalia-shell IPC, suspends on lid)
|
|
Documentation=https://github.com/swaywm/swayidle
|
|
After=graphical-session.target
|
|
# Restart together with the session, not merely at boot: this unit is wanted by
|
|
# default.target, so a compositor restart used to leave it orphaned while the target came
|
|
# back without it. Refs #47.
|
|
PartOf=graphical-session.target
|
|
# With Restart=always below, a fast failure loop must never wedge the watcher into `failed`:
|
|
# a lock that keeps trying beats a lock that stopped trying. Refs #47.
|
|
StartLimitIntervalSec=0
|
|
|
|
[Service]
|
|
# Locks via the noctalia v5 shell; `noctalia msg` resolves the running instance
|
|
# through the D-Bus session bus, so no WAYLAND_DISPLAY pinning is needed in unit context.
|
|
# deploy.sh enables it only where the ExecStart binary resolves, so it stays inert until swayidle is installed.
|
|
ExecStart=/usr/bin/swayidle -w \
|
|
timeout 300 '/usr/bin/noctalia msg session lock' \
|
|
before-sleep '/usr/bin/noctalia msg session lock' \
|
|
after-resume '/usr/bin/noctalia msg session lock'
|
|
# `swayidle -w` exits 0 on SIGTERM, and that is how this watcher died on 2026-10-12: a clean
|
|
# exit that `Restart=on-failure` cannot see, leaving the box unlocked for a week. A supervisor
|
|
# restarts always. Refs #47.
|
|
Restart=always
|
|
RestartSec=5s
|
|
|
|
[Install]
|
|
WantedBy=default.target
|