Summary
When a lock request stalls after lock-pending: screen-stabilizing, lockRequested stays latched true forever. Because the lock plugin derives locked from lockRequested, the lock IPC method then short-circuits and returns "ok" for every subsequent request without ever locking. The lid-close lock therefore becomes a silent no-op and the machine suspends with the session fully exposed. On resume, Hyprland shows its crashed-lockscreen failsafe.
The stall itself is intermittent. The latch that turns one stall into "every later lock silently does nothing, and reports success" is deterministic, and is the part that makes this a security issue rather than a cosmetic one.
What happened
Single boot, no user config beyond stock idle values (screensaver: 150, lock: 300):
08:48:21 omarchy idle: process-start: lock omarchy-system-lock
08:48:21 omarchy lock lock-requested
08:48:21 omarchy lock lock-pending: screen-stabilizing
08:48:21 omarchy idle: process-exit: lock exitCode=0 status=0
<-- no session-locked=true, no secure=true, ever
08:50:51 omarchy idle: process-start: screensaver (so lock isLocked == false)
08:53:21 omarchy idle: process-exit: lock exitCode=0 status=0 (second idle lock, also a no-op)
08:57:44 systemd-logind: Lid closed.
08:57:57 systemd-logind: Suspending...
<-- suspended UNLOCKED
10:52:36 systemd-logind: Lid opened. (resume)
<-- Hyprland crashed-lockscreen failsafe on screen
10:52:43 omarchy lock session-locked=false
10:52:43 omarchy lock unlocked
Between 08:48:21 and 10:52:43 the omarchy lock namespace logged nothing at all. sessionLock.locked = true was reached (no lock-pending: no-real-screen was logged, so hasRealScreen() was true and requestSessionLock() ran to completion), but onLockStateChanged never fired — Quickshell silently declined to take the ext-session-lock.
Expected behavior
A lock request that cannot be satisfied should fail loudly and reset lockRequested, so the next request — in particular the lid-close one — gets a real attempt. A machine must never suspend unlocked because an earlier lock stalled.
Root cause
plugins/lock/Service.qml:
readonly property bool locked: lockRequested || sessionLock.locked || sessionLock.secure
IpcHandler {
target: "lock"
function lock(): string {
if (!root.passwordPamConfigured) return "missing-pam"
if (!root.locked && !root.beginLock()) return "failed"
return "ok"
}
Once lockRequested is true, root.locked is true, so beginLock() is never called again and the method returns "ok". There is no timeout that resets lockRequested when the session lock never materialises — pendingSessionLockTimer retries requestSessionLock() forever, and requestSessionLock() returns immediately at its first guard once lockRequested is set but the lock never arrives.
Nothing downstream can notice, either:
omarchy-system-lock runs omarchy-shell lock lock >/dev/null and never checks the reply.
omarchy-system-sleep-lock's lock_state() reads requested: true, secure: false and classifies it as locking — "the shell has the request and is working on it, so leave it alone" — so it waits out its whole budget instead of re-requesting.
So the one component that could have caught this deliberately stays quiet, because a latched-stuck request is indistinguishable from a healthy in-progress one.
Suggested direction
Give the pending state a deadline. If sessionLock.locked has not become true within some bound of beginLock(), log a failure, reset lockRequested/pendingSessionLock, and let the next caller retry from scratch. lock() returning "ok" should mean "the session is locked or is provably becoming locked", not "a request was recorded at some point".
Distinguishing requested-but-stalled from locking in lock status would also let omarchy-system-sleep-lock re-request rather than wait, which is what it already does for the idle state.
Steps to reproduce
The stall is intermittent and I could not reproduce it on demand. The latch is trivially reproducible once the stall has happened: while lock status reports requested: true, sessionLocked: false, secure: false, every omarchy-shell lock lock returns ok and nothing locks, including omarchy system lock and the lid-close path.
Contrast: on the same shell process (PID unchanged), a manual omarchy-shell lock lock from a clean state reaches secure=true in ~400-600 ms, twice out of two attempts:
0.0s req=True sessLocked=False secure=False last=lock-pending: screen-stabilizing
0.4s req=True sessLocked=True secure=True last=secure=true
Possibly relevant
The shell had been restarted with omarchy restart shell about 8 minutes before the stalled lock (08:40:32; the stall was at 08:48:21). The same process later locked correctly on demand, so a restart is clearly not sufficient to cause it, but it may be part of the trigger and I did not want to leave it out.
omarchy-sleep-lock.service also failed three times on resume with Failed to inhibit: The operation inhibition has been requested for is already running (restart counter 1→4 at 10:52:36-10:52:42), which looks like #7280.
Related
System details
- Omarchy: 4.0.2-1
- Hyprland: 0.56.2
- Quickshell: 0.3.1-1
- Kernel: 7.1.9-arch1-2
- Model: LENOVO Yoga 7 2-in-1 16AKP10 (83JU)
- CPU: AMD Ryzen AI 7 350 w/ Radeon 860M
- GPU: AMD Krackan [Radeon 840M / 860M Graphics]
- Single internal display, no external monitors
/etc/pam.d/omarchy-lock-password present and valid; fingerprint not configured
- All 96 migrations applied
omarchy debug is not available in 4.0.2 (Unknown Omarchy command), so the details above were collected by hand.
Summary
When a lock request stalls after
lock-pending: screen-stabilizing,lockRequestedstays latchedtrueforever. Because the lock plugin deriveslockedfromlockRequested, thelockIPC method then short-circuits and returns"ok"for every subsequent request without ever locking. The lid-close lock therefore becomes a silent no-op and the machine suspends with the session fully exposed. On resume, Hyprland shows its crashed-lockscreen failsafe.The stall itself is intermittent. The latch that turns one stall into "every later lock silently does nothing, and reports success" is deterministic, and is the part that makes this a security issue rather than a cosmetic one.
What happened
Single boot, no user config beyond stock
idlevalues (screensaver: 150,lock: 300):Between 08:48:21 and 10:52:43 the
omarchy locknamespace logged nothing at all.sessionLock.locked = truewas reached (nolock-pending: no-real-screenwas logged, sohasRealScreen()was true andrequestSessionLock()ran to completion), butonLockStateChangednever fired — Quickshell silently declined to take the ext-session-lock.Expected behavior
A lock request that cannot be satisfied should fail loudly and reset
lockRequested, so the next request — in particular the lid-close one — gets a real attempt. A machine must never suspend unlocked because an earlier lock stalled.Root cause
plugins/lock/Service.qml:Once
lockRequestedistrue,root.lockedistrue, sobeginLock()is never called again and the method returns"ok". There is no timeout that resetslockRequestedwhen the session lock never materialises —pendingSessionLockTimerretriesrequestSessionLock()forever, andrequestSessionLock()returns immediately at its first guard oncelockRequestedis set but the lock never arrives.Nothing downstream can notice, either:
omarchy-system-lockrunsomarchy-shell lock lock >/dev/nulland never checks the reply.omarchy-system-sleep-lock'slock_state()readsrequested: true, secure: falseand classifies it aslocking— "the shell has the request and is working on it, so leave it alone" — so it waits out its whole budget instead of re-requesting.So the one component that could have caught this deliberately stays quiet, because a latched-stuck request is indistinguishable from a healthy in-progress one.
Suggested direction
Give the pending state a deadline. If
sessionLock.lockedhas not become true within some bound ofbeginLock(), log a failure, resetlockRequested/pendingSessionLock, and let the next caller retry from scratch.lock()returning"ok"should mean "the session is locked or is provably becoming locked", not "a request was recorded at some point".Distinguishing
requested-but-stalledfromlockinginlock statuswould also letomarchy-system-sleep-lockre-request rather than wait, which is what it already does for theidlestate.Steps to reproduce
The stall is intermittent and I could not reproduce it on demand. The latch is trivially reproducible once the stall has happened: while
lock statusreportsrequested: true, sessionLocked: false, secure: false, everyomarchy-shell lock lockreturnsokand nothing locks, includingomarchy system lockand the lid-close path.Contrast: on the same shell process (PID unchanged), a manual
omarchy-shell lock lockfrom a clean state reachessecure=truein ~400-600 ms, twice out of two attempts:Possibly relevant
The shell had been restarted with
omarchy restart shellabout 8 minutes before the stalled lock (08:40:32; the stall was at 08:48:21). The same process later locked correctly on demand, so a restart is clearly not sufficient to cause it, but it may be part of the trigger and I did not want to leave it out.omarchy-sleep-lock.servicealso failed three times on resume withFailed to inhibit: The operation inhibition has been requested for is already running(restart counter 1→4 at 10:52:36-10:52:42), which looks like #7280.Related
lock-requestedbefore stalling.omarchy-sleep-lock.serviceRestart=alwaysracing logind on resume.System details
/etc/pam.d/omarchy-lock-passwordpresent and valid; fingerprint not configuredomarchy debugis not available in 4.0.2 (Unknown Omarchy command), so the details above were collected by hand.