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.
Session lock silently lost on output re-add: desktop exposed without authentication (and quickshell SIGABRT on the same path)
Reported by nikitaprokopov · filed Sep 6, 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?
- Decided by agents and verified by a second, independent agent — see the decision.
Original report
What happens
A locked session can be lost without any authentication when the compositor finishes theext-session-lock while the shell holds it. Two distinct symptoms, same trigger class
(output re-add / lock re-request while locked):
A. Shell aborts — FATAL: Tried to show lockscreen surfaces without active lock,
quickshell dies with SIGABRT and is restarted by its crash handler.
B. Silent unlock (worse) — no crash at all: WlSessionLock.locked goes to false
and plugins/lock/Service.qml treats that as a normal unlock, wakes the display and
leaves the full desktop exposed. No password is ever asked.
On a laptop whose lid policy is "lock (and hibernate on battery)" this defeats the lock:
the machine comes back from dpms-off / resume showing the unlocked desktop.
Expected
A lock that disappears without a successful PAM authentication must be re-established
(and reported), never treated as an unlock.
Steps to reproduce
Omarchy defaults, stock shell config (Config Path: /usr/share/omarchy/shell/shell.qml),misc:allow_session_lock_restore = true (Omarchy default, default/hypr/looknfeel.lua:115).
omarchy-shell lock lock
sleep 3
omarchy-shell lock status # {"locked":true,"secure":true,...}
omarchy-hyprland-session-locked; echo $? # 0 = compositor holds the lock
# force an output re-add while locked (a resume from suspend/hibernate does the same)
hyprctl dispatch 'hl.dsp.dpms({ state = "off" })'; sleep 2
hyprctl dispatch 'hl.dsp.dpms({ state = "on" })'; sleep 5
omarchy-hyprland-session-locked; echo $? # 1 = lock is GONE
omarchy-shell lock status # {"locked":false,"secure":false,"lastEvent":"unlocked"}omarchy-shell shell rescanPlugins while locked reproduces the same loss (that path tends
to produce symptom A rather than B).
Observed rate on this machine, always starting from secure=true:
| trigger while locked | attempts | lock lost | of those, with SIGABRT |
|---|---|---|---|
omarchy-shell shell rescanPlugins | 5 | 2 | 2 |
dpms off → dpms on | 3 | 1 | 0 |
Where it goes wrong
plugins/lock/Service.qml, onLockStateChanged:
if (!locked && root.lockRequested) {
root.lockRequested = false
...
root.runWake()
}Any transition of sessionLock.locked to false is accepted as "the user unlocked".
The only legitimate producer of that transition is finishUnlock() (line 157) after PAM
success; a compositor-side finished event takes the same branch. There is no
"was this authenticated?" flag, so an involuntary lock loss silently becomes an unlock.
Symptom A comes from the other side of the same situation: requestSessionLock()
(sessionLock.locked = true, line 76) is reached again — from recoverStrandedLock()
(lines 94-100) or the stabilize/pending timers — after the lock was finished, and
quickshell aborts instead of failing the request. Two crash stacks, both ending in a QML
property write:
QQmlTimer::ticked -> QQmlBoundSignalExpression::evaluate -> QObjectWrapper::setProperty
-> FATAL: Tried to show lockscreen surfaces without active lock
QWaylandScreen::maybeInitialize -> QWindowSystemInterface::handleScreenAdded
-> QQmlNotifier::emitNotify -> QObjectWrapper::setProperty
-> FATAL: Tried to show lockscreen surfaces without active lockSuggested fix shape
- Record authentication in
finishUnlock()and, inonLockStateChanged, when the lock
drops without it: logEvent("lock-lost: relocking"), call beginLock() and send a
critical notification instead of runWake().
2. Do not re-request a session lock in the same process after the compositor finished one
(guard requestSessionLock()), which also avoids the quickshell abort. The abort itself
looks like missing hardening on the quickshell side and can be filed there separately.
System
- Omarchy 4.0.2-1, quickshell 0.3.1-1, hyprland 0.56.2-1, Qt 6.11.2
- Kernel 7.1.9-arch1-2, Lenovo Yoga Pro 9 16IMH9 (hybrid Intel Arc iGPU + RTX 4060 Mobile)
- Crash reports available locally:
~/.cache/quickshell/crashes/{7h1u7hwkt,7h1ptiwkt,dc95qgtxkt,dc95e0uxkt}
(Signal: Aborted (6), log tail is the FATAL line above)
- omarchy debug --no-sudo --print output can be attached on request; a screenshot of the
exposed desktop after step 4 exists but is not attached because it shows private content.
View raw text
## What happens
A locked session can be lost without any authentication when the compositor finishes the
`ext-session-lock` while the shell holds it. Two distinct symptoms, same trigger class
(output re-add / lock re-request while locked):
**A. Shell aborts** — `FATAL: Tried to show lockscreen surfaces without active lock`,
quickshell dies with SIGABRT and is restarted by its crash handler.
**B. Silent unlock (worse)** — no crash at all: `WlSessionLock.locked` goes to `false`
and `plugins/lock/Service.qml` treats that as a normal unlock, wakes the display and
leaves the full desktop exposed. No password is ever asked.
On a laptop whose lid policy is "lock (and hibernate on battery)" this defeats the lock:
the machine comes back from dpms-off / resume showing the unlocked desktop.
## Expected
A lock that disappears without a successful PAM authentication must be re-established
(and reported), never treated as an unlock.
## Steps to reproduce
Omarchy defaults, stock shell config (`Config Path: /usr/share/omarchy/shell/shell.qml`),
`misc:allow_session_lock_restore = true` (Omarchy default, `default/hypr/looknfeel.lua:115`).
```bash
omarchy-shell lock lock
sleep 3
omarchy-shell lock status # {"locked":true,"secure":true,...}
omarchy-hyprland-session-locked; echo $? # 0 = compositor holds the lock
# force an output re-add while locked (a resume from suspend/hibernate does the same)
hyprctl dispatch 'hl.dsp.dpms({ state = "off" })'; sleep 2
hyprctl dispatch 'hl.dsp.dpms({ state = "on" })'; sleep 5
omarchy-hyprland-session-locked; echo $? # 1 = lock is GONE
omarchy-shell lock status # {"locked":false,"secure":false,"lastEvent":"unlocked"}
```
`omarchy-shell shell rescanPlugins` while locked reproduces the same loss (that path tends
to produce symptom A rather than B).
Observed rate on this machine, always starting from `secure=true`:
| trigger while locked | attempts | lock lost | of those, with SIGABRT |
|---|---|---|---|
| `omarchy-shell shell rescanPlugins` | 5 | 2 | 2 |
| `dpms off` → `dpms on` | 3 | 1 | 0 |
## Where it goes wrong
`plugins/lock/Service.qml`, `onLockStateChanged`:
```qml
if (!locked && root.lockRequested) {
root.lockRequested = false
...
root.runWake()
}
```
Any transition of `sessionLock.locked` to `false` is accepted as "the user unlocked".
The only legitimate producer of that transition is `finishUnlock()` (line 157) after PAM
success; a compositor-side `finished` event takes the same branch. There is no
"was this authenticated?" flag, so an involuntary lock loss silently becomes an unlock.
Symptom A comes from the other side of the same situation: `requestSessionLock()`
(`sessionLock.locked = true`, line 76) is reached again — from `recoverStrandedLock()`
(lines 94-100) or the stabilize/pending timers — after the lock was finished, and
quickshell aborts instead of failing the request. Two crash stacks, both ending in a QML
property write:
```
QQmlTimer::ticked -> QQmlBoundSignalExpression::evaluate -> QObjectWrapper::setProperty
-> FATAL: Tried to show lockscreen surfaces without active lock
QWaylandScreen::maybeInitialize -> QWindowSystemInterface::handleScreenAdded
-> QQmlNotifier::emitNotify -> QObjectWrapper::setProperty
-> FATAL: Tried to show lockscreen surfaces without active lock
```
## Suggested fix shape
1. Record authentication in `finishUnlock()` and, in `onLockStateChanged`, when the lock
drops without it: `logEvent("lock-lost: relocking")`, call `beginLock()` and send a
critical notification instead of `runWake()`.
2. Do not re-request a session lock in the same process after the compositor finished one
(guard `requestSessionLock()`), which also avoids the quickshell abort. The abort itself
looks like missing hardening on the quickshell side and can be filed there separately.
## System
- Omarchy 4.0.2-1, quickshell 0.3.1-1, hyprland 0.56.2-1, Qt 6.11.2
- Kernel 7.1.9-arch1-2, Lenovo Yoga Pro 9 16IMH9 (hybrid Intel Arc iGPU + RTX 4060 Mobile)
- Crash reports available locally: `~/.cache/quickshell/crashes/{7h1u7hwkt,7h1ptiwkt,dc95qgtxkt,dc95e0uxkt}`
(`Signal: Aborted (6)`, log tail is the FATAL line above)
- `omarchy debug --no-sudo --print` output can be attached on request; a screenshot of the
exposed desktop after step 4 exists but is not attached because it shows private content.
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
- Known issueCompositor-side lock loss with a request outstanding is treated as a finished unlock; no re-lock KI-LOCK-SPONTANEOUS-UNLOCK-FAIL-OPEN· decided by agents · verified
- Fixed bypr/14988· 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.
- DuplicateCompositor-side lock loss with a request outstanding is treated as a finished unlock; no re-lock KI-LOCK-SPONTANEOUS-UNLOCK-FAIL-OPENDecided by agents · verified · 2026-10-05 05:11 UTC