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.
Idle service wakes the display while the session is locked
Reported by kurtome · filed Sep 10, 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: matches no known issue — decided by arbitration; partly in the audited area (lock) — decided by agents · verified. The outcome (valid, duplicate, not a bug, …) is not decided yet. See the decision.
Original report
What happened
While the session is locked and the display is blanked, the idle service turns the display back on. The lock screen then stays lit — often for 15–25 minutes, sometimes indefinitely — with nobody at the machine.
Expected
A locked session stays dark. Nothing but real interaction with the lock screen should power the display back on.
Root cause
shell/plugins/services/idle/Service.qml — cancelIdleCycle() runs omarchy-system-wake (→ omarchy-brightness-display on → hl.dsp.dpms enable) without checking whether the session is locked:
if (root.idledThisCycle) runProcess(wakeProcess, "wake", "omarchy-system-wake")There is a guard in handleActiveSignal() that is meant to keep the cycle armed rather than cancel it:
if (root.screensaverStartedThisCycle && (root.screensaverWindowCount > 0 || screensaverLaunchGraceTimer.running)) {
logEvent("idle-monitor-active", "screensaver cycle remains armed")
return
}but it requires a live screensaver window — and launchScreensaver() deliberately skips the screensaver when the session is locked:
runProcess(screensaverProcess, "screensaver", "[[ $(omarchy-shell lock isLocked 2>/dev/null) == \"true\" ]] || omarchy-launch-screensaver")So while locked, screensaverWindowCount is always 0, the grace timer has long expired, and every activity signal inside an idle cycle falls straight through to cancelIdleCycle("activity") → wake.
Compounding it, nothing re-blanks promptly. shell/plugins/lock/Service.qml's 5-second idleBlankTimer only re-arms from real lock-screen input (LockView.wakeRequested → runWake()) or a fresh beginLock(). A wake originating in the idle service does not re-arm it, so the display stays lit until the next complete idle cycle re-locks.
Note that IdleMonitor has respectInhibitors: true, so an app taking or releasing a Wayland idle inhibitor produces an isIdleChanged → "active" transition with zero user input. That path never touches LockView, so the lock screen never learns it should re-blank.
Steps to reproduce
- Set
idle.screensaverandidle.lockin~/.config/omarchy/shell.json(e.g.600/900). - Let the machine idle until it locks and the display blanks.
- Leave it locked and untouched. Wait for any activity signal to land between
idle-cycle-startand the lock deadline — an idle-inhibitor toggle from a background app is enough; no input required. - The display powers on at the lock screen and stays on.
Evidence
journalctl --user | grep "omarchy idle", machine locked and unattended overnight:
01:19:22 lock-system: lock-timeout <- locked, display blanked
01:20:15 idle-monitor: active
01:30:21 idle-monitor: idle
01:30:21 idle-cycle-start: screensaver=600 lock=900
01:30:21 process-start: screensaver [[ $(omarchy-shell lock isLocked) == "true" ]] || omarchy-launch-screensaver
01:30:21 process-exit: screensaver exitCode=0 <- correctly skipped, so no screensaver window
01:30:32 idle-monitor: active <- 11s in, nobody present
01:30:32 idle-cycle-cancel: activity
01:30:32 process-start: wake omarchy-system-wake <- display ON, still lockedAnother instance where the screen stayed lit for 20 minutes until the user walked up:
23:13:32 idle-monitor: idle
23:13:41 idle-monitor: active
23:13:41 idle-cycle-cancel: activity
23:13:41 process-start: wake omarchy-system-wake
...
23:33:22 unlocked <- lit the whole time18 such wake events in 7 days on this machine.
Suggested fix
Guard the wake the same way launchScreensaver() already guards itself, so the idiom stays consistent within the file:
if (root.idledThisCycle) runProcess(wakeProcess, "wake", "[[ $(omarchy-shell lock isLocked 2>/dev/null) == \"true\" ]] || omarchy-system-wake")This is running locally as a omarchy plugin clone omarchy.idle patch and resolves the symptom. Unlocking still wakes correctly, since lock/Service.qml's finishUnlock() calls its own runWake().
Happy to open a PR if the approach looks right.
System
| Omarchy | 4.0.2-1 |
| Kernel | 7.1.9-arch1-2 |
| Hyprland | 0.56.2 |
| Quickshell | 0.3.1-1 |
| CPU | AMD Ryzen AI MAX+ 395 w/ Radeon 8060S |
| GPU | AMD Strix Halo (Radeon 8060S) |
View raw text
## What happened
While the session is locked and the display is blanked, the idle service turns the display back on. The lock screen then stays lit — often for 15–25 minutes, sometimes indefinitely — with nobody at the machine.
## Expected
A locked session stays dark. Nothing but real interaction with the lock screen should power the display back on.
## Root cause
`shell/plugins/services/idle/Service.qml` — `cancelIdleCycle()` runs `omarchy-system-wake` (→ `omarchy-brightness-display on` → `hl.dsp.dpms enable`) without checking whether the session is locked:
```qml
if (root.idledThisCycle) runProcess(wakeProcess, "wake", "omarchy-system-wake")
```
There is a guard in `handleActiveSignal()` that is meant to keep the cycle armed rather than cancel it:
```qml
if (root.screensaverStartedThisCycle && (root.screensaverWindowCount > 0 || screensaverLaunchGraceTimer.running)) {
logEvent("idle-monitor-active", "screensaver cycle remains armed")
return
}
```
but it requires a live screensaver window — and `launchScreensaver()` deliberately skips the screensaver when the session is locked:
```qml
runProcess(screensaverProcess, "screensaver", "[[ $(omarchy-shell lock isLocked 2>/dev/null) == \"true\" ]] || omarchy-launch-screensaver")
```
So while locked, `screensaverWindowCount` is always 0, the grace timer has long expired, and **every** activity signal inside an idle cycle falls straight through to `cancelIdleCycle("activity")` → wake.
Compounding it, nothing re-blanks promptly. `shell/plugins/lock/Service.qml`'s 5-second `idleBlankTimer` only re-arms from real lock-screen input (`LockView.wakeRequested` → `runWake()`) or a fresh `beginLock()`. A wake originating in the idle service does not re-arm it, so the display stays lit until the *next* complete idle cycle re-locks.
Note that `IdleMonitor` has `respectInhibitors: true`, so an app taking or releasing a Wayland idle inhibitor produces an `isIdleChanged` → "active" transition with **zero** user input. That path never touches `LockView`, so the lock screen never learns it should re-blank.
## Steps to reproduce
1. Set `idle.screensaver` and `idle.lock` in `~/.config/omarchy/shell.json` (e.g. `600` / `900`).
2. Let the machine idle until it locks and the display blanks.
3. Leave it locked and untouched. Wait for any activity signal to land between `idle-cycle-start` and the lock deadline — an idle-inhibitor toggle from a background app is enough; no input required.
4. The display powers on at the lock screen and stays on.
## Evidence
`journalctl --user | grep "omarchy idle"`, machine locked and unattended overnight:
```
01:19:22 lock-system: lock-timeout <- locked, display blanked
01:20:15 idle-monitor: active
01:30:21 idle-monitor: idle
01:30:21 idle-cycle-start: screensaver=600 lock=900
01:30:21 process-start: screensaver [[ $(omarchy-shell lock isLocked) == "true" ]] || omarchy-launch-screensaver
01:30:21 process-exit: screensaver exitCode=0 <- correctly skipped, so no screensaver window
01:30:32 idle-monitor: active <- 11s in, nobody present
01:30:32 idle-cycle-cancel: activity
01:30:32 process-start: wake omarchy-system-wake <- display ON, still locked
```
Another instance where the screen stayed lit for 20 minutes until the user walked up:
```
23:13:32 idle-monitor: idle
23:13:41 idle-monitor: active
23:13:41 idle-cycle-cancel: activity
23:13:41 process-start: wake omarchy-system-wake
...
23:33:22 unlocked <- lit the whole time
```
18 such wake events in 7 days on this machine.
## Suggested fix
Guard the wake the same way `launchScreensaver()` already guards itself, so the idiom stays consistent within the file:
```qml
if (root.idledThisCycle) runProcess(wakeProcess, "wake", "[[ $(omarchy-shell lock isLocked 2>/dev/null) == \"true\" ]] || omarchy-system-wake")
```
This is running locally as a `omarchy plugin clone omarchy.idle` patch and resolves the symptom. Unlocking still wakes correctly, since `lock/Service.qml`'s `finishUnlock()` calls its own `runWake()`.
Happy to open a PR if the approach looks right.
## System
| | |
|---|---|
| Omarchy | 4.0.2-1 |
| Kernel | 7.1.9-arch1-2 |
| Hyprland | 0.56.2 |
| Quickshell | 0.3.1-1 |
| CPU | AMD Ryzen AI MAX+ 395 w/ Radeon 8060S |
| GPU | AMD Strix Halo (Radeon 8060S) |
Also reported as
This defect is tracked under R-731 (filed Sep 10, 2026). 1 other report describes the same defect.
Earliest report of this defect: R-1050 (filed Sep 2, 2026).
- Sep 10, 2026R-731 · this report— tracked here
- ·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
- Fixed bypr/9152· 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 arbitration: matches no known issue
- Decided by agents · verified: partly in the audited area (lock)
The outcome (valid, duplicate, not a bug, …) is not decided yet.