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

Brief black flash on every lock — beginLock() defers refreshBackground() via Qt.callLater while session lock timers start immediately

Reported by AlyKazani04 · filed Sep 15, 2026 · open at source

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

R-1213 is the earliest report of this defect — filed Aug 30, 2026, open upstream. This report (filed Sep 15, 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?
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?
Decided by agents and verified by a second, independent agent — see the decision.

Original report

Brief black flash on every lock — beginLock() defers refreshBackground() via Qt.callLater while session lock timers start immediatelyrevision 1 · captured 2026-09-28 21:23 UTC via poll

System details

Omarchy 4.0.4-1 (quattro branch)

What's wrong?

Every time the session locks (idle timeout or manual lock), there is a brief black flash — the WlSessionLockSurface appears with the solid Color.background color before the wallpaper image loads and the blur renders. The delay is a single frame or two, but noticeable.

This is not related to suspend/resume. It reproduces on every fresh lock.

Steps to reproduce:
1. Have the stock omarchy.lock plugin enabled (no third-party lock plugins)
2. Lock the session (omarchy system lock or idle timeout)
3. Observe: a brief black flash before the blurred wallpaper appears

Root cause:

In shell/plugins/lock/Service.qml, beginLock() defers refreshBackground() via Qt.callLater while queueSessionLock() starts its timers immediately:

function beginLock() {
  // ...
  lockRequested = true       // root.locked = true → loadBackground = true
  armBlankTimer()
  logEvent("lock-requested")
  queueSessionLock()         // 500ms stabilize timer starts NOW

  Qt.callLater(function() {  // deferred to next event loop iteration
    root.refreshBackground() // readlinkProc starts here
    root.refreshFingerprintStatus()
  })

  return true
}

The race:
1. lockRequested = true → root.locked becomes true → LockView.qml's loadBackground binding flips to true
2. queueSessionLock() starts sessionLockStabilizeTimer (500ms) → sessionLock.locked = true shortly after
3. The Image element in LockView.qml starts loading with whatever backgroundPath is currently set
4. Qt.callLater defers refreshBackground() to the next event loop — readlinkProc hasn't resolved the symlink yet
5. If backgroundPath is empty or stale, the Image loads nothing → black screen
6. Once readlinkProc completes and the Image decodes → wallpaper appears

In LockView.qml:

Image {
  id: wallpaper
  source: root.loadBackground ? root.fileUrl(root.backgroundPath) : ""
  asynchronous: true
  cache: false
  // ...
}

loadBackground is bound to root.locked, which is true the instant beginLock() runs. The Image tries to load before backgroundPath is resolved.

Note on lock-explorer plugin:

This race exists on both quattro and v4-0-4 (stable). The io.github.sirjul1337.lock-explorer plugin copies the same Qt.callLater pattern from the stock plugin. Users may notice the flash more after removing lock-explorer if the plugin's different rendering pipeline happened to mask the symptom.

Related issues:
- #7416 — related but different (wallpaper not reloading after suspend/resume, backgroundVersion cache-bust not firing on wake)

Suggested fix:

Move refreshBackground() before queueSessionLock() so backgroundPath is resolved before the session lock activates:

function beginLock() {
  if (!passwordPamConfigured) {
    logEvent("lock-denied: missing-pam")
    return false
  }

  resetAuthenticationState()
  lockRequested = true
  armBlankTimer()
  logEvent("lock-requested")

  refreshBackground()         // resolve path BEFORE arming the lock
  refreshFingerprintStatus()

  queueSessionLock()          // now the 500ms timer runs with a valid backgroundPath

  return true
}

Or keep the Qt.callLater for fingerprint (which is slow) but resolve the background synchronously.


This issue was investigated and written by OpenCode (AI coding agent) on behalf of the user.


Related Images (@AlyKazani04 here):

Shows up for a split second.
<img width="4080" height="2296" alt="Image" src="https://github.com/user-attachments/assets/6bf288d5-9ede-4213-a278-2318a84d2719" />
After
<img width="4080" height="2296" alt="Image" src="https://github.com/user-attachments/assets/67b7edd3-7ade-4093-ab7c-956632b9d561" />

View raw text
## System details

Omarchy 4.0.4-1 (quattro branch)

## What's wrong?

Every time the session locks (idle timeout or manual lock), there is a brief black flash — the `WlSessionLockSurface` appears with the solid `Color.background` color before the wallpaper image loads and the blur renders. The delay is a single frame or two, but noticeable.

This is **not** related to suspend/resume. It reproduces on every fresh lock.

**Steps to reproduce:**
1. Have the stock `omarchy.lock` plugin enabled (no third-party lock plugins)
2. Lock the session (`omarchy system lock` or idle timeout)
3. Observe: a brief black flash before the blurred wallpaper appears

**Root cause:**

In `shell/plugins/lock/Service.qml`, `beginLock()` defers `refreshBackground()` via `Qt.callLater` while `queueSessionLock()` starts its timers immediately:

```qml
function beginLock() {
  // ...
  lockRequested = true       // root.locked = true → loadBackground = true
  armBlankTimer()
  logEvent("lock-requested")
  queueSessionLock()         // 500ms stabilize timer starts NOW

  Qt.callLater(function() {  // deferred to next event loop iteration
    root.refreshBackground() // readlinkProc starts here
    root.refreshFingerprintStatus()
  })

  return true
}
```

The race:
1. `lockRequested = true` → `root.locked` becomes true → `LockView.qml`'s `loadBackground` binding flips to `true`
2. `queueSessionLock()` starts `sessionLockStabilizeTimer` (500ms) → `sessionLock.locked = true` shortly after
3. The `Image` element in `LockView.qml` starts loading with whatever `backgroundPath` is currently set
4. `Qt.callLater` defers `refreshBackground()` to the next event loop — `readlinkProc` hasn't resolved the symlink yet
5. If `backgroundPath` is empty or stale, the Image loads nothing → **black screen**
6. Once `readlinkProc` completes and the Image decodes → wallpaper appears

In `LockView.qml`:

```qml
Image {
  id: wallpaper
  source: root.loadBackground ? root.fileUrl(root.backgroundPath) : ""
  asynchronous: true
  cache: false
  // ...
}
```

`loadBackground` is bound to `root.locked`, which is true the instant `beginLock()` runs. The Image tries to load before `backgroundPath` is resolved.

**Note on lock-explorer plugin:**

This race exists on both `quattro` and `v4-0-4` (stable). The `io.github.sirjul1337.lock-explorer` plugin copies the same `Qt.callLater` pattern from the stock plugin. Users may notice the flash more after removing lock-explorer if the plugin's different rendering pipeline happened to mask the symptom.

**Related issues:**
- #7416 — related but different (wallpaper not reloading after suspend/resume, `backgroundVersion` cache-bust not firing on wake)

**Suggested fix:**

Move `refreshBackground()` before `queueSessionLock()` so `backgroundPath` is resolved before the session lock activates:

```qml
function beginLock() {
  if (!passwordPamConfigured) {
    logEvent("lock-denied: missing-pam")
    return false
  }

  resetAuthenticationState()
  lockRequested = true
  armBlankTimer()
  logEvent("lock-requested")

  refreshBackground()         // resolve path BEFORE arming the lock
  refreshFingerprintStatus()

  queueSessionLock()          // now the 500ms timer runs with a valid backgroundPath

  return true
}
```

Or keep the `Qt.callLater` for fingerprint (which is slow) but resolve the background synchronously.

---
*This issue was investigated and written by OpenCode (AI coding agent) on behalf of the user.*

---
Related Images (@AlyKazani04 here): 

Shows up for a split second.
<img width="4080" height="2296" alt="Image" src="https://github.com/user-attachments/assets/6bf288d5-9ede-4213-a278-2318a84d2719" />
After
<img width="4080" height="2296" alt="Image" src="https://github.com/user-attachments/assets/67b7edd3-7ade-4093-ab7c-956632b9d561" />
  • 6bf288d5-9ede-4213-a278-2318a84d2719
  • 67b7edd3-7ade-4093-ab7c-956632b9d561

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-1213Decided by agents · verified · 2026-10-09 03:23 UTC