Proof Portal
Omarchy
ProbeLabs65 findings · 95 requirementsA proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.
Stalled lock latches lockRequested, so every later lock() returns "ok" without locking — machine suspends unlocked
Reported by mchldotdev · filed Sep 5, 2026 · open at source
At a glance
- 1 · What is claimed?
- Only the report text below — no validator has restated it as checkable claims yet.
- 2 · What does it affect?
- 3 · Is it reproducible?
- No validation has been run yet.
- 4 · What remains unclear?
- Not validated yet: no claims, no reproducer.
- 5 · What happens next?
- Our staff decide whether the report is valid. Where this project allows it, agents decide whether it duplicates another report or a known issue. So far: in the audited area (lock) — decided by agents · verified; not a duplicate of another report — decided by agents · verified; matches no known issue — decided by agents · verified. The outcome (valid, duplicate, not a bug, …) is not decided yet. See the decision.
Original report
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 unlockedBetween 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.secureIpcHandler {
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-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.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=truePossibly 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
- #9480 — idle-triggered lock silently no-ops. Related but distinct: there the lock module logs nothing at all, so the IPC never reaches it. Here the IPC arrives and the module logs
lock-requestedbefore stalling. - #9156 — shell lock client dies on resume, showing the same Hyprland failsafe.
- #7280 —
omarchy-sleep-lock.serviceRestart=alwaysracing logind on resume.
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-passwordpresent 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.
View raw text
## 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`:
```qml
readonly property bool locked: lockRequested || sessionLock.locked || sessionLock.secure
```
```qml
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
- #9480 — idle-triggered lock silently no-ops. Related but distinct: there the lock module logs nothing at all, so the IPC never reaches it. Here the IPC arrives and the module logs `lock-requested` before stalling.
- #9156 — shell lock client dies on resume, showing the same Hyprland failsafe.
- #7280 — `omarchy-sleep-lock.service` `Restart=always` racing logind on resume.
## 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.
Also reported as
R-942 is the earliest report of this defect, filed Sep 5, 2026. 1 later report describes the same defect.
- Sep 5, 2026R-942 · this report— the original
- ·Decided by agents · verified
Open upstream.
Validation
Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.
Linked
- Related PRpr/9429· confirmed
- Related PRpr/14911· confirmed
- Fixed bypr/14974· confirmed
Decision
Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.
- Decided by agents · verified: in the audited area (lock)
- Decided by agents · verified: not a duplicate of another report
- Decided by agents · verified: matches no known issue
The outcome (valid, duplicate, not a bug, …) is not decided yet.