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-1332GitHub issuePublicDecided· Out of scope

Theme change can leave one monitor on the old wallpaper, and `background refresh` cannot recover it (multi-GPU, multi-monitor)

Reported by nicolasbadano · filed Aug 27, 2026 · open at source

Decided: Out of scope — decided by agents · verified 4 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

Theme change can leave one monitor on the old wallpaper, and `background refresh` cannot recover it (multi-GPU, multi-monitor)revision 1 · captured 2026-09-28 21:24 UTC via poll

System details

Intel i7-12700 + two GPUs (NVIDIA Quadro T400 driving DP-1, Intel UHD 770 driving HDMI-A-1), Omarchy 4.0.1-1, Hyprland 0.56.2, quickshell 0.3.1-1, Qt 6.11.2, mesa 26.2.1, kernel 7.1.9-arch1-2, Lenovo ThinkStation P360.

Two monitors, both scale 1.25: DP-1 2560x1440 at 0,864 (NVIDIA, primary DRM device) and HDMI-A-1 1920x1080 at 256,0 (Intel, secondary DRM device — aquamarine logs Starting backend for /dev/dri/card0, with driver i915 with primary /dev/dri/card1 and GBM: Buffer is marked as multigpu, forcing linear).

What's wrong?

omarchy theme set kanagawa retinted everything correctly and updated the wallpaper on DP-1, but HDMI-A-1 kept displaying the previous theme's wallpaper indefinitely. The bar on that same monitor kept updating normally (clock ticking, new theme colors), so only the background surface was stale.

What I verified

  1. Shell state was correct. ~/.local/state/omarchy/current/background -> .../current/theme/backgrounds/1-kanagawa.jpg, and theme.name said kanagawa.
  2. Only the pixels were wrong. Sampling the visible wallpaper strip on each output with grim + ImageMagick:
  • HDMI-A-1: #58206F (previous theme's purple)
  • DP-1: #C0C1B4 (matches 1-kanagawa.jpg, which averages #A1A09A)
  1. hyprctl layers showed exactly one omarchy-background layer per monitor, both from the shell's pid, both correctly sized/positioned. No stacking or leftover-surface problem.
  2. omarchy restart shell fixed it immediately — HDMI-A-1 then sampled #C0C1B4, matching DP-1.

Since every panel binds its image to the same shared root properties, correct state plus wrong pixels means that output's background surface simply never committed a new frame after the source changed, and the compositor kept scanning out its last buffer. A wallpaper is static, so nothing ever forces another redraw and it stays stale forever.

The stale output is precisely the one behind the cross-GPU blit path, so the stale surface itself may well be an aquamarine/Hyprland multi-GPU issue rather than an Omarchy one. But there are two things in the shell that turn a one-frame glitch into an unrecoverable one, and both look fixable here:

1. `omarchy-shell background refresh` cannot recover this state (the obvious user fix silently does nothing).

In shell/plugins/background/Background.qml, refreshBackground() -> readlinkProc -> setBackground(path, false) -> transitionBackground(...), which early-returns:

if (!path || (!force && finalPath === currentBackground)) return

The shell already believes it is showing the new wallpaper, so refresh is a guaranteed no-op exactly when you need it. The same applies to the set and setInstant IPC calls. Only themeTransition passes force: true, and calling that by hand also overwrites the palette. It would help a lot if refresh forced a re-set (or if there were any IPC entry point that re-commits the current background without touching colors).

2. The theme reveal only ever runs on one monitor.

maybeStartReveal() is gated on root.revealProgress !== 0, both immediately and again inside its Qt.callLater:

function maybeStartReveal() {
  if (!root.incomingBackground || root.revealProgress !== 0 || maskReady) return
  ...
}

revealProgress is a single shared root property driven by a single shared revealAnimation. Whichever panel's incomingFrame loads first calls startReveal(), which starts the animation and immediately moves revealProgress off 0 — so every other panel's queued callLater re-checks the gate, bails, and never gets maskReady = true. Its incomingLayer therefore stays hidden for the whole transition, and that monitor is left depending entirely on the single revealAnimation.onFinished -> displayedBackground -> base reload to pick up the new image, with no repaint of its own along the way.

So on a multi-monitor setup the cross-fade is only ever seen on one monitor (cosmetic), and the other monitors take a code path with strictly fewer chances to repaint (how this became visible).

Steps to reproduce

  1. Two monitors, ideally driven by different GPUs.
  2. omarchy theme set <some-theme> with a shell that has been running a while.
  3. Watch: the reveal animation plays on one monitor only; occasionally the other monitor keeps the old wallpaper.
  4. omarchy-shell background refresh does nothing. omarchy restart shell fixes it.

I could not make step 3 fire on demand — it happened once after ~45 h of shell uptime, during which HDMI-A-1 had been disconnected and reconnected once (aquamarine logged Deinitializing secondary renderer on /dev/dri/card0, no enabled outputs and later reinitialized it). That hotplug is a plausible trigger for the output's render path getting into a state where it no longer repaints on its own.

Possibly related: #8024 (bar foreground stuck at old opacity on one monitor after a shell-wide toggle) looks like the same shape — shared root state updated, one monitor's panel not reflecting it.

Workaround

omarchy restart shell

View raw text
### System details

Intel i7-12700 + **two GPUs** (NVIDIA Quadro T400 driving DP-1, Intel UHD 770 driving HDMI-A-1), Omarchy 4.0.1-1, Hyprland 0.56.2, quickshell 0.3.1-1, Qt 6.11.2, mesa 26.2.1, kernel 7.1.9-arch1-2, Lenovo ThinkStation P360.

Two monitors, both scale 1.25: `DP-1` 2560x1440 at `0,864` (NVIDIA, **primary** DRM device) and `HDMI-A-1` 1920x1080 at `256,0` (Intel, **secondary** DRM device — aquamarine logs `Starting backend for /dev/dri/card0, with driver i915 with primary /dev/dri/card1` and `GBM: Buffer is marked as multigpu, forcing linear`).

### What's wrong?

`omarchy theme set kanagawa` retinted everything correctly and updated the wallpaper on DP-1, but **HDMI-A-1 kept displaying the previous theme's wallpaper indefinitely**. The bar on that same monitor kept updating normally (clock ticking, new theme colors), so only the background surface was stale.

**What I verified**

1. Shell state was correct. `~/.local/state/omarchy/current/background` -> `.../current/theme/backgrounds/1-kanagawa.jpg`, and `theme.name` said `kanagawa`.
2. Only the pixels were wrong. Sampling the visible wallpaper strip on each output with `grim` + ImageMagick:
   - `HDMI-A-1`: `#58206F` (previous theme's purple)
   - `DP-1`: `#C0C1B4` (matches `1-kanagawa.jpg`, which averages `#A1A09A`)
3. `hyprctl layers` showed exactly one `omarchy-background` layer per monitor, both from the shell's pid, both correctly sized/positioned. No stacking or leftover-surface problem.
4. `omarchy restart shell` fixed it immediately — HDMI-A-1 then sampled `#C0C1B4`, matching DP-1.

Since every panel binds its image to the same shared root properties, correct state plus wrong pixels means **that output's background surface simply never committed a new frame** after the source changed, and the compositor kept scanning out its last buffer. A wallpaper is static, so nothing ever forces another redraw and it stays stale forever.

The stale output is precisely the one behind the cross-GPU blit path, so the stale surface itself may well be an aquamarine/Hyprland multi-GPU issue rather than an Omarchy one. But there are two things in the shell that turn a one-frame glitch into an unrecoverable one, and both look fixable here:

**1. `omarchy-shell background refresh` cannot recover this state (the obvious user fix silently does nothing).**

In `shell/plugins/background/Background.qml`, `refreshBackground()` -> `readlinkProc` -> `setBackground(path, false)` -> `transitionBackground(...)`, which early-returns:

```qml
if (!path || (!force && finalPath === currentBackground)) return
```

The shell already believes it is showing the new wallpaper, so `refresh` is a guaranteed no-op exactly when you need it. The same applies to the `set` and `setInstant` IPC calls. Only `themeTransition` passes `force: true`, and calling that by hand also overwrites the palette. It would help a lot if `refresh` forced a re-set (or if there were any IPC entry point that re-commits the current background without touching colors).

**2. The theme reveal only ever runs on one monitor.**

`maybeStartReveal()` is gated on `root.revealProgress !== 0`, both immediately and again inside its `Qt.callLater`:

```qml
function maybeStartReveal() {
  if (!root.incomingBackground || root.revealProgress !== 0 || maskReady) return
  ...
}
```

`revealProgress` is a single shared root property driven by a single shared `revealAnimation`. Whichever panel's `incomingFrame` loads first calls `startReveal()`, which starts the animation and immediately moves `revealProgress` off 0 — so every other panel's queued `callLater` re-checks the gate, bails, and never gets `maskReady = true`. Its `incomingLayer` therefore stays hidden for the whole transition, and that monitor is left depending entirely on the single `revealAnimation.onFinished` -> `displayedBackground` -> `base` reload to pick up the new image, with no repaint of its own along the way.

So on a multi-monitor setup the cross-fade is only ever seen on one monitor (cosmetic), and the other monitors take a code path with strictly fewer chances to repaint (how this became visible).

### Steps to reproduce

1. Two monitors, ideally driven by different GPUs.
2. `omarchy theme set <some-theme>` with a shell that has been running a while.
3. Watch: the reveal animation plays on one monitor only; occasionally the other monitor keeps the old wallpaper.
4. `omarchy-shell background refresh` does nothing. `omarchy restart shell` fixes it.

I could not make step 3 fire on demand — it happened once after ~45 h of shell uptime, during which HDMI-A-1 had been disconnected and reconnected once (aquamarine logged `Deinitializing secondary renderer on /dev/dri/card0, no enabled outputs` and later reinitialized it). That hotplug is a plausible trigger for the output's render path getting into a state where it no longer repaints on its own.

Possibly related: #8024 (bar foreground stuck at old opacity on one monitor after a shell-wide toggle) looks like the same shape — shared root state updated, one monitor's panel not reflecting it.

### Workaround

`omarchy restart shell`

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/9312· confirmed
  • Related PRpr/9313· 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 09:40 UTC