Incident: session-lock crash after shell restart (2026-10-20)
Severity: High (forced reboot, no data loss) Status: Mitigated; long-term resilience tracked separately Tracking: HorneroOS/shell#255 (closed), HorneroOS/shell#257 (open)
Summary
On 2026-10-20 the development host rebooted twice after the graphical
session locked while Quickshell was stopped/restarted for deployment.
When the Quickshell process died, its WlSessionLock surface died with
it, Hyprland fell back to its crashed-lockscreen recovery screen, and
the only safe recovery was a reboot from a TTY. The user password was
never bypassed and no data was lost.
Root cause analysis
- The Hornero shell owns the session lock in-process:
modules/lock/Lock.qmlcreates a singleWlSessionLockviaLock.requestLock()(see HorneroOS/shelldocs/LOCKING.md). - Stopping or restarting Quickshell therefore destroys the lock surface of an already-locked session. The compositor cannot re-create that surface from a new process, so it shows the crashed-lockscreen fallback and the session is unrecoverable in place.
- Contributing factor: the restart was issued during long CI waits while the session had idle-locked, so the lock state was not obvious from the operator side.
- Detection gap found during the fix: logind
LockedHintstaysnounder the shell lock on Hyprland, and a runninghyprlockprocess may not exist at all (the lock is the shell’s own surface). Neither signal alone can be trusted to mean “not locked”.
Fix shipped
horneroctl shell stop|restartnow refuses while the session is locked (HorneroOS/hornero PR #197).--dry-runstays allowed so the plan can be inspected.- The authoritative lock probe is the shell itself:
qs ipc call lock isLocked, tried against the caller’sQS_CONFIG_PATHfirst and then the resolved shell config directory (HorneroOS/hornero PR #199). logindLockedHintand ahyprlockprocess probe remain only as fallbacks; all probes fail open so a probe error never blocks an unlocked restart. - Live proof on the affected host: with the shell lock engaged, the
rebuilt
horneroctlrefusedshell stop --yeswith “refusing to stop the shell while the session is locked” and the lock stayed engaged. - Operator rule documented in HorneroOS/shell
docs/LOCKING.md: never stop/restart Quickshell while locked; unlock first. Recovery from an already-crashed lock is TTY-only and documented there.
What did not work
- Trusting logind
LockedHint: it reportednowhile the shell lock was visibly engaged. - Trusting a
hyprlockprocess probe: the lock belongs to the shell process, so nohyprlockprocess exists in this configuration.
Remaining risk
If the shell process crashes spontaneously while locked (OOM, segfault), the same compositor fallback can still occur; no QML or CLI change can repair an orphaned compositor lock after the fact. HorneroOS/shell#257 tracks evaluating resilience options (for example a minimal external lock supervisor) without weakening the lock’s security model.
Prevention checklist
- Unlock the session before any
horneroctl shell stop|restart. - Rely on the built-in refusal; do not add
--force-style bypasses. - After any shell restart on a host, confirm with
qs ipc call lock isLockedandhorneroctl shell status --json. - Keep
MemoryMaxcaps on local builds so a build cannot OOM the shell process while the session is locked.