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-431GitHub issuePublicFixed

Screensaver self-exits within ~1s, canceling the pending idle lock too

Reported by charlestakang · filed Sep 17, 2026 · open at source

Decided: Out of scope — decided by agents · verified 3 days ago.

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?
AreaOutside the audited area· 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?
Decided by agents and verified by a second, independent agent — see the decision.

Original report

Screensaver self-exits within ~1s, canceling the pending idle lock toorevision 1 · captured 2026-09-28 21:23 UTC via poll

What happened

The idle screensaver (omarchy-launch-screensaver / omarchy-screensaver) intermittently opens and then closes itself again about 1 second later, before it's ever perceptible on screen. Because the shell's idle service (Service.qml) treats the screensaver window closing as "the user dismissed it," it also cancels the entire pending idle cycle — including the separately-timed idle lock. So on affected cycles, neither the screensaver nor the idle lock actually happens.

Confirmed via journalctl --user (idle service debug logging), 3 real idle cycles logged in one session, idle.screensaver=150, idle.lock=300:

# Cycle 1 (worked correctly — screensaver stayed up ~2m10s until real dismissal)
idle-monitor: idle
idle-cycle-start: screensaver=150 lock=300
process-start: screensaver [...] || omarchy-launch-screensaver
idle-monitor: active
idle-monitor-active: screensaver cycle remains armed
process-exit: screensaver exitCode=0 status=0
... (~2m10s later, real user input) ...
idle-cycle-cancel: screensaver-dismissed
process-start: wake omarchy-system-wake

# Cycle 2 (bug — self-exits ~1.1s after launch, no real input)
idle-monitor: idle
idle-cycle-start: screensaver=150 lock=300
process-start: screensaver [...]
idle-monitor: active
idle-monitor-active: screensaver cycle remains armed
process-exit: screensaver exitCode=0 status=0
idle-cycle-cancel: screensaver-dismissed      # only ~1.1s after process-exit
process-start: wake omarchy-system-wake

# Cycle 3 (same bug, same ~1.1-1.2s timing)

Root cause (traced)

omarchy-screensaver's inner watchdog loop:

while pgrep -t "${tty#/dev/}" -x ttfx >/dev/null; do
  if read -n1 -t 1 || ! screensaver_in_focus; then
    exit_screensaver
  fi
done

screensaver_in_focus() checks hyprctl activewindow -j | jq -e '.class == "org.omarchy.screensaver"' once per second. On a race (window mapped but Hyprland hasn't settled focus onto it yet, or follow_mouse=1 focus-follow timing), this check fails on its first poll and the script self-terminates via exit_screensaver, closing the window almost immediately after it opened.

The shell's idle service (omarchy-shell/plugins/services/idle/Service.qml, handleScreensaverWindowClosed) then correctly-by-design treats any screensaver window close during an active idle cycle as user dismissal and calls cancelIdleCycle("screensaver-dismissed"), which also stops the still-pending lockTimer. So the bug doesn't just hide the screensaver — it silently defeats idle-lock too.

Expected

The screensaver should reliably stay up until real user input (keyboard/mouse) is received, and a spurious self-exit from the screensaver script should not be indistinguishable from a genuine user dismissal for lock-cancellation purposes.

Environment

  • Omarchy version: 4.0.4-1
  • Kernel: 7.2.5-3-omarchy
  • CPU: AMD Ryzen 5 Microsoft Surface (R) Edition
  • GPU: AMD Picasso/Raven 2 (Radeon Vega, integrated)
  • Machine: Microsoft Surface Laptop 3 (AMD 15" edition)
  • Single monitor (eDP-1), follow_mouse = 1 (Hyprland default)
  • ttfx 0.3.2

Workaround in use

Disabled the screensaver locally via omarchy toggle screensaver off, which removes the racy window-close path entirely and lets the idle lock fire reliably again.

View raw text
## What happened

The idle screensaver (`omarchy-launch-screensaver` / `omarchy-screensaver`) intermittently opens and then closes itself again about 1 second later, before it's ever perceptible on screen. Because the shell's idle service (`Service.qml`) treats the screensaver window closing as "the user dismissed it," it also cancels the *entire* pending idle cycle — including the separately-timed idle lock. So on affected cycles, neither the screensaver nor the idle lock actually happens.

Confirmed via `journalctl --user` (idle service debug logging), 3 real idle cycles logged in one session, `idle.screensaver=150`, `idle.lock=300`:

```
# Cycle 1 (worked correctly — screensaver stayed up ~2m10s until real dismissal)
idle-monitor: idle
idle-cycle-start: screensaver=150 lock=300
process-start: screensaver [...] || omarchy-launch-screensaver
idle-monitor: active
idle-monitor-active: screensaver cycle remains armed
process-exit: screensaver exitCode=0 status=0
... (~2m10s later, real user input) ...
idle-cycle-cancel: screensaver-dismissed
process-start: wake omarchy-system-wake

# Cycle 2 (bug — self-exits ~1.1s after launch, no real input)
idle-monitor: idle
idle-cycle-start: screensaver=150 lock=300
process-start: screensaver [...]
idle-monitor: active
idle-monitor-active: screensaver cycle remains armed
process-exit: screensaver exitCode=0 status=0
idle-cycle-cancel: screensaver-dismissed      # only ~1.1s after process-exit
process-start: wake omarchy-system-wake

# Cycle 3 (same bug, same ~1.1-1.2s timing)
```

## Root cause (traced)

`omarchy-screensaver`'s inner watchdog loop:

```bash
while pgrep -t "${tty#/dev/}" -x ttfx >/dev/null; do
  if read -n1 -t 1 || ! screensaver_in_focus; then
    exit_screensaver
  fi
done
```

`screensaver_in_focus()` checks `hyprctl activewindow -j | jq -e '.class == "org.omarchy.screensaver"'` once per second. On a race (window mapped but Hyprland hasn't settled focus onto it yet, or `follow_mouse=1` focus-follow timing), this check fails on its first poll and the script self-terminates via `exit_screensaver`, closing the window almost immediately after it opened.

The shell's idle service (`omarchy-shell/plugins/services/idle/Service.qml`, `handleScreensaverWindowClosed`) then correctly-by-design treats any screensaver window close during an active idle cycle as user dismissal and calls `cancelIdleCycle("screensaver-dismissed")`, which also stops the still-pending `lockTimer`. So the bug doesn't just hide the screensaver — it silently defeats idle-lock too.

## Expected

The screensaver should reliably stay up until real user input (keyboard/mouse) is received, and a spurious self-exit from the screensaver script should not be indistinguishable from a genuine user dismissal for lock-cancellation purposes.

## Environment

- Omarchy version: 4.0.4-1
- Kernel: 7.2.5-3-omarchy
- CPU: AMD Ryzen 5 Microsoft Surface (R) Edition
- GPU: AMD Picasso/Raven 2 (Radeon Vega, integrated)
- Machine: Microsoft Surface Laptop 3 (AMD 15" edition)
- Single monitor (eDP-1), `follow_mouse = 1` (Hyprland default)
- `ttfx` 0.3.2

## Workaround in use

Disabled the screensaver locally via `omarchy toggle screensaver off`, which removes the racy window-close path entirely and lets the idle lock fire reliably again.

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/12730· confirmed
  • Fixed bypr/12808· 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.

  • Out of scopeDecided by agents · verified · 2026-10-09 02:26 UTC