Proof Portal

Project overview

Omarchy

ProbeLabs65 findings · 95 requirements

A 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.

Inbox
ReportR-1608GitHub issuePublicFixed

Display never blanks after lock: blank/wake silently stops working in a long-running shell

Reported by mkraku · filed Aug 21, 2026 · open at source

In the audited area (lock) — decided by agents · verified. Not validated yet.

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?
AreaIn the audited arealock· Decided by agents · verified
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

Display never blanks after lock: blank/wake silently stops working in a long-running shellrevision 1 · captured 2026-09-28 21:29 UTC via poll

System details

Omarchy 4.0.0-1 · Hyprland 0.56.2 · quickshell 0.3.0.r20.g28771c7-1 · Arch Linux
QEMU/KVM VM with iGPU passthrough: AMD Ryzen 7 8745H (Radeon 780M) driving a ViewSonic VX2880-4K over DP-2, single monitor.

What's wrong?

After locking, the display never powers off. The lock service's 5-second blank (idleBlankTimer → runBlank() in shell/plugins/lock/Service.qml) silently does nothing once the shell process has been running for a long time (~24h in my case). runWake() uses the same spawn pattern, so the worse variant of this bug — a display that won't come back on input — is latent in the same code path.

What I verified on the failing shell, the same day it was wedged:

  • Locked via omarchy-shell lock lock and polled hyprctl monitors -j: dpmsStatus stayed true at +7.5s, +35s, and minutes later.
  • Idle behavior was otherwise healthy: the screensaver fired at 150s and lock at 300s on schedule all day (journal), and Quickshell Process spawns from the idle service (screensaver / lock / wake) kept working.
  • The blank command chain works from every context I could run it: a login shell, the shell process's exact environment (/proc/<pid>/environ), and while the session is locked (the display stays off — nothing re-enables DPMS underneath the lock surface).
  • No input occurred during the failing window (the Quickshell idle monitor still fired on schedule), so it is not pointer jitter re-arming the timer.
  • beginLock() completed — the lock-requested journal event logs only after armBlankTimer() has run.

Minimal reproduction in a fresh process (works): I extracted the exact pattern — Timer { interval: 5000 } with the suspend freeze-guard, plus a Process { command: ["bash", "-c", "omarchy-brightness-keyboard off; omarchy-brightness-display off"] } started via running = true — into a standalone quickshell instance. The timer fired at 5284ms, the process spawned, and the display blanked and restored. The pattern itself is fine in a fresh process; only the long-running shell fails.

Suspected mechanism: a Quickshell Process whose child exit was missed leaves running == true, after which the if (!process.running) guard in runBlank() / runWake() silently skips every later spawn until the shell is restarted. I could not observe the property directly (it is not exposed over IPC), but every other link in the chain is verified working, and the failure was permanent across many locks until restart.

Recovery: omarchy restart shell restores blanking — dpmsStatus went false at lock+5s right after a restart and stayed off (verified twice).

Local mitigation: I cloned the lock service via omarchy plugin clone omarchy.lock and switched runBlank/runWake to stateless Quickshell.execDetached([...]) plus one idempotent retry 5s later (cancelled by any wake interaction). The journal now shows blank-display and blank-display-retry events exactly at lock+5s/+10s.

Suggestions:

  • Spawn blank/wake statelessly (Quickshell.execDetached) or create a fresh Process per run instead of guarding on a reused running property.
  • A logEvent line in runBlank() would make this class of silent failure visible in the journal.

Debug log: https://logs.omarchy.org/lWKPEB

View raw text
## System details

Omarchy 4.0.0-1 · Hyprland 0.56.2 · quickshell 0.3.0.r20.g28771c7-1 · Arch Linux
QEMU/KVM VM with iGPU passthrough: AMD Ryzen 7 8745H (Radeon 780M) driving a ViewSonic VX2880-4K over DP-2, single monitor.

## What's wrong?

After locking, the display never powers off. The lock service's 5-second blank (`idleBlankTimer` → `runBlank()` in `shell/plugins/lock/Service.qml`) silently does nothing once the shell process has been running for a long time (~24h in my case). `runWake()` uses the same spawn pattern, so the worse variant of this bug — a display that won't come back on input — is latent in the same code path.

What I verified on the failing shell, the same day it was wedged:

- Locked via `omarchy-shell lock lock` and polled `hyprctl monitors -j`: `dpmsStatus` stayed `true` at +7.5s, +35s, and minutes later.
- Idle behavior was otherwise healthy: the screensaver fired at 150s and lock at 300s on schedule all day (journal), and Quickshell `Process` spawns from the idle service (screensaver / lock / wake) kept working.
- The blank command chain works from every context I could run it: a login shell, the shell process's exact environment (`/proc/<pid>/environ`), and while the session is locked (the display stays off — nothing re-enables DPMS underneath the lock surface).
- No input occurred during the failing window (the Quickshell idle monitor still fired on schedule), so it is not pointer jitter re-arming the timer.
- `beginLock()` completed — the `lock-requested` journal event logs only after `armBlankTimer()` has run.

Minimal reproduction in a fresh process (works): I extracted the exact pattern — `Timer { interval: 5000 }` with the suspend freeze-guard, plus a `Process { command: ["bash", "-c", "omarchy-brightness-keyboard off; omarchy-brightness-display off"] }` started via `running = true` — into a standalone quickshell instance. The timer fired at 5284ms, the process spawned, and the display blanked and restored. The pattern itself is fine in a fresh process; only the long-running shell fails.

Suspected mechanism: a Quickshell `Process` whose child exit was missed leaves `running == true`, after which the `if (!process.running)` guard in `runBlank()` / `runWake()` silently skips every later spawn until the shell is restarted. I could not observe the property directly (it is not exposed over IPC), but every other link in the chain is verified working, and the failure was permanent across many locks until restart.

Recovery: `omarchy restart shell` restores blanking — `dpmsStatus` went `false` at lock+5s right after a restart and stayed off (verified twice).

Local mitigation: I cloned the lock service via `omarchy plugin clone omarchy.lock` and switched `runBlank`/`runWake` to stateless `Quickshell.execDetached([...])` plus one idempotent retry 5s later (cancelled by any wake interaction). The journal now shows `blank-display` and `blank-display-retry` events exactly at lock+5s/+10s.

Suggestions:

- Spawn blank/wake statelessly (`Quickshell.execDetached`) or create a fresh `Process` per run instead of guarding on a reused `running` property.
- A `logEvent` line in `runBlank()` would make this class of silent failure visible in the journal.

Debug log: https://logs.omarchy.org/lWKPEB

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/7673· confirmed
  • Related PRpr/8581· 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.