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.
Lock screen blanks ~5 seconds after resume: suspend-gap guard re-arms the blank timer instead of holding the display
Reported by callumau · filed Sep 12, 2026 · open at source
At a glance
- 1 · What is claimed?
- ConfirmedReproducible: after a resume the frozen idle countdown re-arms and the lock screen blanks about 5 s later with no input; a resume 5-7 s after arming blanks at once
- Not confirmedAffects the lock component
- 2 · What does it affect?
- 3 · Is it reproducible?
- An engine-executed run exited 1 and showed the defect signal. See reproducer
- 4 · What remains unclear?
- Open question for maintainers: Open decision: after a resume from suspend, what should the lock screen do with the displays? Options: (A) keep the current behaviour (re-arm, blank one 5 s interval after the resume; SW-REQ-260912-ND55 as written); (B) stay lit until the next user input (the report's proposal); (C) stay lit for a grace period chosen by the maintainers, then blank.
- Open question for maintainers: Related, independent of the option above: should a resume 5-7 s after the countdown was armed (neither branch of the guard today; blanks at once) count as a resume? Approving an option amends the suspend clause of SW-REQ-260912-ND55.
- Open question for maintainers: The decision is the Omarchy maintainers'; a draft requirement listing these options sits on this report's branch (report/cmulr8v3s0je91gw4wt4k3bat), and approving one option = merging that draft into the baseline with the chosen text.
- Affects the lock component — no requirement of the lock component is confirmed as violated
- 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; intended behaviour — a design question, not a defect — decided by agents · verified. The outcome (valid, duplicate, not a bug, …) is not decided yet. See the decision.
Open question — draft spec awaiting a decision
After a resume from suspend, what shall the lock screen do with the displays?
(upstream quattro 035ce29f) idleBlankTimer (Service.qml:730-748, interval 5000 ms) re-arms when wall-clock time shows the countdown froze through a suspend (Service.qml:739-742), so with no input the displays blank 5 s after the resume; a resume between 5 s and 7 s after arming takes neither branch and blanks at once. This is what SW-REQ-260912-ND55 says today.
- AKeep the current behaviour
- BAfter a resume the displays shall stay lit until the next user input
- CAfter a resume the displays shall stay lit for a grace period chosen by the maintainers, then blank
Separately: whether a resume 5-7 s after arming counts as a resume. Approving this draft means choosing one option; it then amends the suspend clause of SW-REQ-260912-ND55.
Why this is an open question
Raised by omacom/omarchy#11411 (proof inbox R-655). The validation (report/cmulr8v3s0je91gw4wt4k3bat, rval-1de6fd92d7df97879d5e2230af3730de) reproduced the blank 5.0 s after the resume-time fire and the immediate blank in the 5-7 s window with the real Service.qml timer. SW-REQ-260912-ND55 specifies the re-arm, while its own rationale states the intent not to blank the freshly woken unlock screen: the requirement and its intent disagree. The reporter's follow-up in the issue tested a 60 s blank interval on a Thunderbolt dock.
Proposed on this report's branch @ 31eeed97ea · specs/software/requirements/SW-REQ-261005-4S1M.req.yaml · not part of the audited spec until approved and merged
Written after the validation was sealed at 790596e81f: the seal covers the reproducer and the observed behaviour, not this proposed text.
Original report
System details
Framework Laptop 13 (AMD Ryzen 5 7640U, Radeon 760M, amdgpu), Thunderbolt dock with 2x DELL S2721DGF over DP, Omarchy 4.0.3-1, Hyprland 0.56.2-2, quickshell 0.3.1-1, kernel 7.2.3-arch1-3.
(omarchy debug is not an available command in 4.0.3, so no debug log; journal timeline included below.)
What's wrong?
After waking the laptop from suspend, the lock screen is visible for about five seconds, then all displays blank (DPMS off). Typing the password still works — each keypress wakes the displays again (submitPassword() -> runWake()) — but the freshly woken lock screen goes dark under the user.
The Thunderbolt dock makes this easy to hit: on resume the dock's DP outputs re-enumerate and a recreated output comes back with DPMS on (the same mechanism documented in #11330's root-cause analysis), so the lock screen lights up on its own without any input — and then the blank timer turns it off again.
Steps to reproduce
- Dock the laptop, lock the session, close the lid / let it suspend.
- Wake the machine.
- The lock screen appears, then blanks ~5 seconds after resume.
Root cause
shell/plugins/lock/Service.qml arms a 5-second blank timer at lock and re-arms it on every wake:
idleBlankTimer(interval: 5000) ->blankProcess->omarchy-brightness-display off->hyprctl dispatch 'hl.dsp.dpms({ action = "disable" })'runWake()re-arms it on every input while locked
QML timers freeze across suspend and fire right after resume. The wall-clock guard in idleBlankTimer.onTriggered exists for exactly this case, but it re-arms the timer instead of cancelling it:
onTriggered: {
// A countdown frozen by suspend fires right after resume, which would
// blank the freshly woken unlock screen under the user. Wall-clock time
// exposes the gap: take a fresh run-up instead of blanking.
if (Date.now() - armedAt > interval + 2000) {
root.armBlankTimer()
return
}
...
}The comment states the intent — don't blank the freshly woken unlock screen under the user — but re-arming guarantees exactly that blank, 5 seconds after resume. The guard postpones the blank by one interval; it does not prevent it.
Journal timeline from one cycle (lock ~64s before suspend, so the timer had already fired and the displays were DPMS-off before sleep; the dock's output re-enumeration at resume is what lit them again):
| Time | Event |
|---|---|
| 12:28:11 | lock-requested / secure=true (blank timer armed, fires at 12:28:16, displays DPMS-off) |
| 12:29:15 | PM: suspend entry (s2idle) |
| 12:34:08 | PM: suspend exit — dock outputs re-enumerate, panels light with DPMS reset to on |
| 12:34:13 (approx) | lock screen blanks again |
| 12:34:16 | user types password -> runWake() -> displays on -> unlocked |
The same blank also lands when the lock happens seconds before suspend (lid-close -> omarchy-sleep-lock -> logind suspends right after secure=true): the timer is frozen mid-countdown and blanks 5s after resume even without the output-recreation path.
Expected
After waking the machine, the lock screen stays visible until the user interacts. Any input already re-arms the normal 5s countdown via runWake().
Actual
The lock screen goes dark ~5 seconds after resume; the user must type blind or press a key to bring it back.
Proposed fix
On the suspend-gap branch, hold the display instead of scheduling a blank:
if (Date.now() - armedAt > interval + 2000) {
// Freshly resumed: the user woke this machine to unlock it. Hold the
// display up; their next input re-arms the normal countdown via runWake().
idleBlankTimer.stop()
return
}Trade-off: an unattended wake (WoL, rtcwake) leaves the lock screen lit until input. That is cosmetic, and the same is already true today whenever an output is recreated with DPMS reset — which the quattro branch already acknowledges ("A panel coming back is a display turning on that runWake did not ask for"). A more conservative variant would re-arm with a longer post-resume grace (30-60s).
Still present on the quattro branch as of this report.
Related issues
- #11330 — output recreation resets DPMS to on (why the lock screen lights up on its own at resume)
- #8361 — lock screen loses keyboard focus after resume (compounds this: after the blank, a click may be needed before typing works)
View raw text
### System details
Framework Laptop 13 (AMD Ryzen 5 7640U, Radeon 760M, amdgpu), Thunderbolt dock with 2x DELL S2721DGF over DP, Omarchy 4.0.3-1, Hyprland 0.56.2-2, quickshell 0.3.1-1, kernel 7.2.3-arch1-3.
(`omarchy debug` is not an available command in 4.0.3, so no debug log; journal timeline included below.)
### What's wrong?
After waking the laptop from suspend, the lock screen is visible for about five seconds, then all displays blank (DPMS off). Typing the password still works — each keypress wakes the displays again (`submitPassword()` -> `runWake()`) — but the freshly woken lock screen goes dark under the user.
The Thunderbolt dock makes this easy to hit: on resume the dock's DP outputs re-enumerate and a recreated output comes back with DPMS **on** (the same mechanism documented in #11330's root-cause analysis), so the lock screen lights up on its own without any input — and then the blank timer turns it off again.
**Steps to reproduce**
1. Dock the laptop, lock the session, close the lid / let it suspend.
2. Wake the machine.
3. The lock screen appears, then blanks ~5 seconds after resume.
**Root cause**
`shell/plugins/lock/Service.qml` arms a 5-second blank timer at lock and re-arms it on every wake:
- `idleBlankTimer` (`interval: 5000`) -> `blankProcess` -> `omarchy-brightness-display off` -> `hyprctl dispatch 'hl.dsp.dpms({ action = "disable" })'`
- `runWake()` re-arms it on every input while locked
QML timers freeze across suspend and fire right after resume. The wall-clock guard in `idleBlankTimer.onTriggered` exists for exactly this case, but it **re-arms** the timer instead of cancelling it:
```qml
onTriggered: {
// A countdown frozen by suspend fires right after resume, which would
// blank the freshly woken unlock screen under the user. Wall-clock time
// exposes the gap: take a fresh run-up instead of blanking.
if (Date.now() - armedAt > interval + 2000) {
root.armBlankTimer()
return
}
...
}
```
The comment states the intent — don't blank the freshly woken unlock screen under the user — but re-arming guarantees exactly that blank, 5 seconds after resume. The guard postpones the blank by one interval; it does not prevent it.
Journal timeline from one cycle (lock ~64s before suspend, so the timer had already fired and the displays were DPMS-off before sleep; the dock's output re-enumeration at resume is what lit them again):
| Time | Event |
|---|---|
| 12:28:11 | `lock-requested` / `secure=true` (blank timer armed, fires at 12:28:16, displays DPMS-off) |
| 12:29:15 | `PM: suspend entry (s2idle)` |
| 12:34:08 | `PM: suspend exit` — dock outputs re-enumerate, panels light with DPMS reset to on |
| 12:34:13 (approx) | lock screen blanks again |
| 12:34:16 | user types password -> `runWake()` -> displays on -> `unlocked` |
The same blank also lands when the lock happens seconds *before* suspend (lid-close -> `omarchy-sleep-lock` -> logind suspends right after `secure=true`): the timer is frozen mid-countdown and blanks 5s after resume even without the output-recreation path.
**Expected**
After waking the machine, the lock screen stays visible until the user interacts. Any input already re-arms the normal 5s countdown via `runWake()`.
**Actual**
The lock screen goes dark ~5 seconds after resume; the user must type blind or press a key to bring it back.
**Proposed fix**
On the suspend-gap branch, hold the display instead of scheduling a blank:
```qml
if (Date.now() - armedAt > interval + 2000) {
// Freshly resumed: the user woke this machine to unlock it. Hold the
// display up; their next input re-arms the normal countdown via runWake().
idleBlankTimer.stop()
return
}
```
Trade-off: an unattended wake (WoL, rtcwake) leaves the lock screen lit until input. That is cosmetic, and the same is already true today whenever an output is recreated with DPMS reset — which the quattro branch already acknowledges ("A panel coming back is a display turning on that runWake did not ask for"). A more conservative variant would re-arm with a longer post-resume grace (30-60s).
Still present on the `quattro` branch as of this report.
**Related issues**
- #11330 — output recreation resets DPMS to on (why the lock screen lights up on its own at resume)
- #8361 — lock screen loses keyboard focus after resume (compounds this: after the blank, a click may be needed before typing works)
Validation
AI validator claude-code (claude-opus-5.5) · evaluated revision 1 at product commit 1e43373287af · sealed 2026-10-05T14:29:55Z
Validation of omacom/omarchy#11411 on quattro-proof 1e433732 (upstream quattro 035ce29f product code plus the proof layer). The reported behaviour reproduces with the real idleBlankTimer of Service.qml (Service.qml:730-748 upstream, interval 5000 ms): a countdown that fires after a suspend (armedAt 20 s in the past) takes the wall-clock guard (Service.qml:739-742) and re-arms, and with no input the displays blank 5 s after that resume-time fire. A resume 6 s after arming (between the 5 s interval and the 7 s guard, the case in the thread's second comment) blanks at the fire itself. This is exactly what SW-REQ-260912-ND55 says ('When wall-clock time shows the timer froze through a suspend, the service shall re-arm the timer instead of blanking'), while ND55's own rationale states the intent that the freshly woken unlock screen is not blanked under the user. The requirement and its stated intent disagree, so the report is not a violation of the spec as written; what the lock should do after a resume is an open specification question for the maintainers. KI-LOCK-SCREENS-CHANGE-BLANK-LOST (screens added/removed while locked) was examined: a different path (onScreensChanged), not this report.
- ConfirmedReproducible
Reproducible: after a resume the frozen idle countdown re-arms and the lock screen blanks about 5 s later with no input; a resume 5-7 s after arming blanks at once
Engine: engine-executed run exited 1 and showed the defect signal
Evidence: file test/reports/report-cmulr8v3s0je91gw4wt4k3bat.sh
Validator's rationale (commentary): Observed with the real Service.qml idleBlankTimer: displaysBlank flips 5.0 s after the resume-time fire; with armedAt 6 s back it flips at the fire. This conforms to SW-REQ-260912-ND55 as written; the question is whether ND55 states the intended behaviour.
Affects the lock component
Engine: no requirement of the lock component is confirmed as violated
Engine reference checks at 790596e81f2c (10/10 ok)
- okcommit790596e81f2c74b5ecc5728977dc29e1c3720c94
- okbase commit1e43373287afbd6dca4674ad463d5b5db7812358
- okbase ancestry1e43373287afbd6dca4674ad463d5b5db7812358
- okproduct code untouchedthis report's reproducer branch
- okbranchthis report's reproducer branch
- okfiletest/reports/report-cmulr8v3s0je91gw4wt4k3bat.qml
- okfiletest/reports/report-cmulr8v3s0je91gw4wt4k3bat.sh
- okrequirementSW-REQ-260912-ND55
- okcomponentlock
- okknown issueKI-LOCK-SCREENS-CHANGE-BLANK-LOST
The engine's own check, at validation time, that the commits, the reproducer branch (touching only reproducer files), the reproducer files and every mapped requirement / component / known issue really exist.
Validator-authored hypothesis. Each claim's verification is engine-computed: confirmed only from an engine-executed reproducer run recorded in this record, refuted when recorded evidence contradicts it. Prose is commentary. The disposition of the report is a human decision.
Reproducer
bash test/reports/report-cmulr8v3s0je91gw4wt4k3bat.sh
Setup failure: exit 2 means the reproducer could not set up (e.g. the product did not build) — such a run decides nothing.
Defect signal: output contains AS-REPORTED:
AS-REPORTED: after a resume the lock screen blanks about 5 s later with no input (the suspend-gap guard re-arms the countdown); a resume 5-7 s after arming blanks at once
Observed on this tree (quickshell 0.3.1, lock plugin shell/plugins/lock): - idleBlankTimer interval 5000 ms; the session is locked, no password check in flight - resume 20 s after arming: at the frozen timer's fire the displays stay lit; the timer is re-armed for another 5000 ms - with no input after that, the displays blank 5 s after the resume-time fire - resume 6 s after arming (between the 5 s interval and the 7 s guard): the displays blank at the fire itself AS-REPORTED: after a resume the lock screen blanks about 5 s later with no input (the suspend-gap guard re-arms the countdown); a resume 5-7 s after arming blanks at once
Needs: linux · quickshell (0.3.x) and jq on PATH; a sway binary for a private headless compositor (PROOF_ENV_DIR/sysroot/usr/bin/sway, else sway on PATH); the script exits 2 with SETUP: otherwise. Nothing to build: the QML is loaded straight from the worktree · ran on linux/amd64
Linked
- Related PRpr/8773· confirmed
- Related PRpr/8561· 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: intended behaviour — a design question, not a defect
The outcome (valid, duplicate, not a bug, …) is not decided yet.