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-392GitHub issuePublicDecided· Duplicate

Idle blank is undone by the idle wake path when the monitor's link flaps, strobing black/lock screen

Reported by lo-nau · filed Sep 17, 2026 · open at source

Decided: Duplicate — decided by agents · verified 3 days ago.

R-1659 is the earliest report of this defect — filed Aug 19, 2026, open upstream. This report (filed Sep 17, 2026) describes the same defect, so it is folded under it in the inbox.

Decided by agents · verified· Oct 9, 2026

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

Original report

Idle blank is undone by the idle wake path when the monitor's link flaps, strobing black/lock screenrevision 1 · captured 2026-09-28 21:23 UTC via poll

What happened

Omarchy 4.0.4-1, idle enabled (omarchy-toggle-idle allow-idle, stay-awake off), stock idle.screensaver=150 / idle.lock=300, single external display.

When the idle lock blanks the panel, the panel does not stay dark. It strobes black → lock screen → black every ~10–20 s, the shell exits with status 255 and relaunches, the lock is re-asserted as stranded, and the lock surface can never be created. The machine cannot be left idle.

Trigger, stated up front

The display's link flaps HPD when the output is disabled, so this needs a display where that is true (here: Philips 288E2 over a Macmini8,1 T2 internal DP→HDMI path, reported as DP-3, 3840x2160@50). Probing the kernel connector, hyprctl monitors all -j and the Hyprland event socket while a blank was held:

 0.30s kernel[status=connected enabled=enabled dpms=On]  hypr[DP-3]
 1.02s kernel[status=connected enabled=disabled dpms=Off] hypr[DP-3]   <- the blank; the link HOLDS ~11 s
12.26s kernel[status=connected enabled=enabled dpms=On]  hypr[DP-3]   <- re-lit, with nobody at the machine
13.92s kernel[status=disconnected]                                     <- sink drops HPD 1.7 s later
14.24s HYPR monitorremoved>>DP-3  /  monitoradded>>FALLBACK
14.54s kernel[status=connected]

The HPD flap is the sink's. The blank itself held. What I think is Omarchy's part is what happens next.

What Omarchy does with it

1. The blank is undone by the idle wake path. Once the output disappears, quickshell has no screens and the screensaver window goes with it. The idle service reads that as a dismissal, cancels the cycle and runs the wake path, which re-enables DPMS and re-triggers the flap:

omarchy idle ... idle-cycle-cancel: screensaver-dismissed
omarchy idle ... process-start: wake omarchy-system-wake

A screensaver window that disappeared because its output disappeared is not user activity. This looks like the same code path as #11133 (cancelIdleCycle() running omarchy-system-wake with no lock check) and #12225 (the screensaver window closing cancels the whole cycle), but with a trigger neither covers: output loss during the blank itself.

2. The shell exits 255 and relaunches, then cannot lock. The new instance recovers a stranded lock and fails to create a surface while there are no outputs:

uwsm_hyprland.desktop[1169]: error in client communication (pid 1244)
omarchy-shell[1244]: WARN: The Wayland connection experienced a fatal error: Invalid argument
omarchy-shell[423716]: Omarchy shell exited with status 255; relaunching.
omarchy-shell[423718]: omarchy lock ... lock-stranded: recovering
omarchy-shell[423718]: Not creating lock surface for screen QScreen(0x..., name="") as it is not backed by a valid wayland output

Same family as #11213 (exit 255 when the FALLBACK monitor is removed), #11253 (crash loop when idle/screensaver triggers with FALLBACK) and #11794.

Expected

  • A blanked/locked session stays dark. A window or output disappearing is not user activity, and must not run the wake path or cancel the pending cycle.
  • Losing a monitor should not exit the shell with 255; a lock should survive a monitor flap and recreate its surface when the output comes back.

Steps to reproduce

Requires a display whose link drops when its output is disabled.

  1. omarchy-toggle-idle allow-idle (stay-awake off)
  2. leave the session idle until the lock timeout, or run omarchy-shell lock lock
  3. watch the panel while running journalctl --user -f | grep -E 'omarchy (idle|lock)'

Without such a display, the idle-side half should be reproducible by removing the only monitor during an active idle cycle (unplug/replug, or a monitor change that leaves the compositor with no outputs). I have not tested that variant, so treat it as a suggestion.

Workaround on this machine

omarchy-toggle-idle stay-awake — disables the idle lock/screensaver/blank entirely and the loop stops. Verified: zero There are no outputs events after the revert. Not a fix, it just avoids the trigger.

Diagnostics

omarchy debug is not a command in 4.0.4-1 (omarchy commands --all lists no debug entry), so there is no debug log; the raw journal lines are quoted above.

  • omarchy 4.0.4-1, hyprland 0.56.2-2, quickshell 0.3.1-1
  • Macmini8,1 (T2), Intel UHD 630 / i915, single external display as DP-3
  • No screenshot/recording: reproducing means triggering an idle blank, and it strobes the user's only display. Happy to record one if it would help.
Probe used (kernel connector + hyprctl + Hyprland event socket)
#!/usr/bin/env python3
"""Hold a DPMS blank and record what the kernel, the compositor and its client
event stream each say. Read-only except for the deliberate dpms disable/enable."""
import json, socket, subprocess, threading, time

SIG = subprocess.run(["hyprctl", "instances"], capture_output=True, text=True).stdout
SIG = [l for l in SIG.splitlines() if l.startswith("instance ")][0].split(" ", 1)[1].rstrip(":")
SOCK = f"/run/user/1000/hypr/{SIG}/.socket2.sock"
DRM = "/sys/class/drm/card1-DP-3"
T0 = time.time(); events = []; stop = threading.Event()
stamp = lambda: f"{time.time() - T0:6.2f}s"

def watch():
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); s.connect(SOCK); s.settimeout(0.5)
    buf = b""
    while not stop.is_set():
        try: chunk = s.recv(4096)
        except socket.timeout: continue
        if not chunk: break
        buf += chunk
        while b"\n" in buf:
            line, buf = buf.split(b"\n", 1)
            ev = line.decode(errors="replace")
            if any(k in ev.split(">>", 1)[0] for k in ("monitor", "dpms", "window")):
                events.append(f"{stamp()} HYPR {ev[:160]}")

def drm():
    r = lambda n: open(f"{DRM}/{n}").read().strip()
    return f"status={r('status')} enabled={r('enabled')} dpms={r('dpms')}"

def hypr():
    m = json.loads(subprocess.run(["hyprctl", "monitors", "all", "-j"],
                                  capture_output=True, text=True).stdout)
    return " ".join(f"{x['name']}:disabled={x['disabled']}:dpms={x['dpmsStatus']}" for x in m)

threading.Thread(target=watch, daemon=True).start(); time.sleep(0.3)
print(f"{stamp()} baseline kernel[{drm()}] hypr[{hypr()}]")
subprocess.run(["hyprctl", "dispatch", 'hl.dsp.dpms({ action = "disable" })'], capture_output=True)
prev = None; start = time.time()
while time.time() - start < 25:
    cur = f"{drm()}|{hypr()}"
    if cur != prev:
        print(f"{stamp()} change   kernel[{drm()}] hypr[{hypr()}]"); prev = cur
    time.sleep(0.2)
subprocess.run(["hyprctl", "dispatch", 'hl.dsp.dpms({ action = "enable" })'], capture_output=True)
time.sleep(4); stop.set(); time.sleep(0.6)
print("\n".join(events))
View raw text
## What happened

Omarchy 4.0.4-1, idle enabled (`omarchy-toggle-idle allow-idle`, stay-awake off), stock `idle.screensaver=150` / `idle.lock=300`, single external display.

When the idle lock blanks the panel, the panel does not stay dark. It strobes black → lock screen → black every ~10–20 s, the shell exits with status 255 and relaunches, the lock is re-asserted as stranded, and the lock surface can never be created. The machine cannot be left idle.

## Trigger, stated up front

The display's link flaps HPD when the output is disabled, so this needs a display where that is true (here: Philips 288E2 over a Macmini8,1 T2 internal DP→HDMI path, reported as `DP-3`, 3840x2160@50). Probing the kernel connector, `hyprctl monitors all -j` and the Hyprland event socket while a blank was held:

```
 0.30s kernel[status=connected enabled=enabled dpms=On]  hypr[DP-3]
 1.02s kernel[status=connected enabled=disabled dpms=Off] hypr[DP-3]   <- the blank; the link HOLDS ~11 s
12.26s kernel[status=connected enabled=enabled dpms=On]  hypr[DP-3]   <- re-lit, with nobody at the machine
13.92s kernel[status=disconnected]                                     <- sink drops HPD 1.7 s later
14.24s HYPR monitorremoved>>DP-3  /  monitoradded>>FALLBACK
14.54s kernel[status=connected]
```

The HPD flap is the sink's. The blank itself held. What I think is Omarchy's part is what happens next.

## What Omarchy does with it

**1. The blank is undone by the idle wake path.** Once the output disappears, quickshell has no screens and the screensaver window goes with it. The idle service reads that as a dismissal, cancels the cycle and runs the wake path, which re-enables DPMS and re-triggers the flap:

```
omarchy idle ... idle-cycle-cancel: screensaver-dismissed
omarchy idle ... process-start: wake omarchy-system-wake
```

A screensaver window that disappeared because its output disappeared is not user activity. This looks like the same code path as #11133 (`cancelIdleCycle()` running `omarchy-system-wake` with no lock check) and #12225 (the screensaver window closing cancels the whole cycle), but with a trigger neither covers: output loss during the blank itself.

**2. The shell exits 255 and relaunches, then cannot lock.** The new instance recovers a stranded lock and fails to create a surface while there are no outputs:

```
uwsm_hyprland.desktop[1169]: error in client communication (pid 1244)
omarchy-shell[1244]: WARN: The Wayland connection experienced a fatal error: Invalid argument
omarchy-shell[423716]: Omarchy shell exited with status 255; relaunching.
omarchy-shell[423718]: omarchy lock ... lock-stranded: recovering
omarchy-shell[423718]: Not creating lock surface for screen QScreen(0x..., name="") as it is not backed by a valid wayland output
```

Same family as #11213 (exit 255 when the FALLBACK monitor is removed), #11253 (crash loop when idle/screensaver triggers with FALLBACK) and #11794.

## Expected

- A blanked/locked session stays dark. A window or output disappearing is not user activity, and must not run the wake path or cancel the pending cycle.
- Losing a monitor should not exit the shell with 255; a lock should survive a monitor flap and recreate its surface when the output comes back.

## Steps to reproduce

Requires a display whose link drops when its output is disabled.

1. `omarchy-toggle-idle allow-idle` (stay-awake off)
2. leave the session idle until the lock timeout, or run `omarchy-shell lock lock`
3. watch the panel while running `journalctl --user -f | grep -E 'omarchy (idle|lock)'`

Without such a display, the idle-side half should be reproducible by removing the only monitor during an active idle cycle (unplug/replug, or a monitor change that leaves the compositor with no outputs). I have not tested that variant, so treat it as a suggestion.

## Workaround on this machine

`omarchy-toggle-idle stay-awake` — disables the idle lock/screensaver/blank entirely and the loop stops. Verified: zero `There are no outputs` events after the revert. Not a fix, it just avoids the trigger.

## Diagnostics

`omarchy debug` is not a command in 4.0.4-1 (`omarchy commands --all` lists no debug entry), so there is no debug log; the raw journal lines are quoted above.

- omarchy 4.0.4-1, hyprland 0.56.2-2, quickshell 0.3.1-1
- Macmini8,1 (T2), Intel UHD 630 / i915, single external display as `DP-3`
- No screenshot/recording: reproducing means triggering an idle blank, and it strobes the user's only display. Happy to record one if it would help.

<details>
<summary>Probe used (kernel connector + hyprctl + Hyprland event socket)</summary>

```python
#!/usr/bin/env python3
"""Hold a DPMS blank and record what the kernel, the compositor and its client
event stream each say. Read-only except for the deliberate dpms disable/enable."""
import json, socket, subprocess, threading, time

SIG = subprocess.run(["hyprctl", "instances"], capture_output=True, text=True).stdout
SIG = [l for l in SIG.splitlines() if l.startswith("instance ")][0].split(" ", 1)[1].rstrip(":")
SOCK = f"/run/user/1000/hypr/{SIG}/.socket2.sock"
DRM = "/sys/class/drm/card1-DP-3"
T0 = time.time(); events = []; stop = threading.Event()
stamp = lambda: f"{time.time() - T0:6.2f}s"

def watch():
    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); s.connect(SOCK); s.settimeout(0.5)
    buf = b""
    while not stop.is_set():
        try: chunk = s.recv(4096)
        except socket.timeout: continue
        if not chunk: break
        buf += chunk
        while b"\n" in buf:
            line, buf = buf.split(b"\n", 1)
            ev = line.decode(errors="replace")
            if any(k in ev.split(">>", 1)[0] for k in ("monitor", "dpms", "window")):
                events.append(f"{stamp()} HYPR {ev[:160]}")

def drm():
    r = lambda n: open(f"{DRM}/{n}").read().strip()
    return f"status={r('status')} enabled={r('enabled')} dpms={r('dpms')}"

def hypr():
    m = json.loads(subprocess.run(["hyprctl", "monitors", "all", "-j"],
                                  capture_output=True, text=True).stdout)
    return " ".join(f"{x['name']}:disabled={x['disabled']}:dpms={x['dpmsStatus']}" for x in m)

threading.Thread(target=watch, daemon=True).start(); time.sleep(0.3)
print(f"{stamp()} baseline kernel[{drm()}] hypr[{hypr()}]")
subprocess.run(["hyprctl", "dispatch", 'hl.dsp.dpms({ action = "disable" })'], capture_output=True)
prev = None; start = time.time()
while time.time() - start < 25:
    cur = f"{drm()}|{hypr()}"
    if cur != prev:
        print(f"{stamp()} change   kernel[{drm()}] hypr[{hypr()}]"); prev = cur
    time.sleep(0.2)
subprocess.run(["hyprctl", "dispatch", 'hl.dsp.dpms({ action = "enable" })'], capture_output=True)
time.sleep(4); stop.set(); time.sleep(0.6)
print("\n".join(events))
```

</details>

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

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.

  • DuplicateR-1659Decided by agents · verified · 2026-10-09 09:00 UTC