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

4.0.3: a third-party panel's failed write to the now read-only centerHoverRevealSuppressed locks the whole session

Reported by weedwhitesandwine · filed Sep 9, 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

4.0.3: a third-party panel's failed write to the now read-only centerHoverRevealSuppressed locks the whole sessionrevision 1 · captured 2026-09-28 21:23 UTC via poll

System details

Intel Core Ultra 7 255H, Omarchy 4.0.3-1 (omarchy-settings 4.0.3-1), Quickshell 0.3.1-1, Hyprland 0.56.2-2, qt6-base 6.11.2-3, kernel 7.2.3-arch1-3, Wayland. omarchy debug output available on request.

What's wrong

On 4.0.3, opening a third-party bar-widget panel made the entire session unresponsive — no keyboard input and no mouse clicks in any application, including the bar itself. Only a omarchy restart shell from a TTY recovered it.

The underlying plugin bug is mine and is already fixed on my side. I'm filing this because of the escalation: a single failed property write inside a plugin's close() is currently enough to make the whole desktop unusable, with no way for the user to recover from within the session.

Two separate things combine to produce that.

1. centerHoverRevealSuppressed became read-only with no deprecation path

shell/Ui/PluginBarApi.qml:26 now exposes the property as readonly, with a setter added alongside it at line 44:

property bool _centerHoverRevealSuppressed: false
readonly property bool centerHoverRevealSuppressed: _centerHoverRevealSuppressed
...
function setCenterHoverRevealSuppressed(value) { ... }

The first-party clock and weather panels were updated to prefer the setter (shell/plugins/panels/clock/Panel.qml:121):

function setCenterHoverRevealSuppressed(value) {
  if (root.bar && typeof root.bar.setCenterHoverRevealSuppressed === "function")
    root.bar.setCenterHoverRevealSuppressed(value)
  else if (root.bar && "centerHoverRevealSuppressed" in root.bar)
    root.bar.centerHoverRevealSuppressed = value
}

A third-party plugin written against the previous API does the direct assignment, and the natural defensive guard does not help — the property still exists, so "centerHoverRevealSuppressed" in root.bar is true. Only the assignment throws:

TypeError: Cannot assign to read-only property "centerHoverRevealSuppressed"

There is no load-time warning and nothing in the plugin docs marking the old form as removed, so the first signal is a runtime throw in the field.

2. A throw in the owner's close() leaves KeyboardPanel holding input

That call sits at the top of the panel's close(), so the exception aborts the function before it can hide anything:

function close() {
  setCenterHoverRevealSuppressed(false)   // throws
  root.controller.hide()                  // never runs
}

Every dismissal route goes through that same close() — Esc via onCloseRequested, outside-click via KeyboardPanel.close() → owner.close(), and re-clicking the bar icon via toggle(). All three die on the same line, so the panel opens but can never close.

KeyboardPanel is a full-screen layer-shell surface on the overlay layer whose keyboard focus follows open (shell/Ui/KeyboardPanel.qml:98):

WlrLayershell.keyboardFocus: open
  ? (focusPrimed ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive)
  : WlrKeyboardFocus.None

Since open never goes false, a screen-sized overlay keeps keyboard focus and swallows pointer input indefinitely:

lvl=0 ns=omarchy-background 1920x1200
lvl=2 ns=omarchy-bar        1920x35
lvl=3 ns=omarchy-keyboard-panel 1920x1200   <-- never unmaps

Opening still worked because openFromHotkey() calls controller.show() before the failing line and defers the suppression call via Qt.callLater, so the panel maps fine and only traps the user on the way out.

Steps to reproduce

  1. In any third-party bar-widget panel built on KeyboardPanel, set root.bar.centerHoverRevealSuppressed = value directly (the pre-4.0.3 form) as the first statement of the panel's close().
  2. omarchy restart shell, then click the widget's bar icon to open the panel.
  3. Try to dismiss it — Esc, click outside, click the icon again.

Expected: the panel closes.
Actual: it never closes, and keyboard plus pointer input is unavailable session-wide. Recovery requires omarchy restart shell from a TTY or over SSH.

Evidence

From the frozen shell process — one error per dismissal attempt, at 00:55:13, :14, :15, :16, :25, :26, :29, :31, :34, :35, :47, :48, :52, :54, :56, 00:56:00, :03, :06, until the recovery restart:

WARN scene: file:///home/<user>/.config/omarchy/plugins/<plugin>/Panel.qml[77:-1]:
  TypeError: Cannot assign to read-only property "centerHoverRevealSuppressed"
  (exception occurred during delayed function evaluation)
...
INFO: Exiting due to IPC request.

Sampling hyprctl layers -j once a second throughout confirmed omarchy-keyboard-panel stayed mapped at full screen size for the entire freeze. The compositor was never wedged — only the dismissal path was.

Suggested hardening

The plugin-side mistake is mine to fix, and I have. But a plugin exception escalating to an unrecoverable session seems worth guarding against:

  1. Don't let an owner's `close()` failure strand the surface. KeyboardPanel.close() could wrap the owner.close() call so that a throw still releases keyboard focus (or forces open = false). Currently a single bad line in third-party QML costs the user their session.
  2. Make the deprecation visible. Either keep centerHoverRevealSuppressed writable and forward the write to setCenterHoverRevealSuppressed(), or warn once at load when a plugin holds the old expectation — anything that surfaces before a user is locked out.
  3. Possibly a compositor-level escape hatch: a keybinding that force-closes any plugin-owned overlay holding exclusive/on-demand focus would make this class of bug self-recoverable.

Happy to test a patch on this machine, or to provide the full omarchy debug output and the raw hyprctl layers capture from the freeze.

View raw text
### System details

Intel Core Ultra 7 255H, Omarchy `4.0.3-1` (`omarchy-settings 4.0.3-1`), Quickshell `0.3.1-1`, Hyprland `0.56.2-2`, qt6-base `6.11.2-3`, kernel `7.2.3-arch1-3`, Wayland. `omarchy debug` output available on request.

### What's wrong

On 4.0.3, opening a third-party bar-widget panel made the **entire session unresponsive** — no keyboard input and no mouse clicks in any application, including the bar itself. Only a `omarchy restart shell` from a TTY recovered it.

The underlying plugin bug is mine and is already fixed on my side. I'm filing this because of the *escalation*: a single failed property write inside a plugin's `close()` is currently enough to make the whole desktop unusable, with no way for the user to recover from within the session.

Two separate things combine to produce that.

### 1. `centerHoverRevealSuppressed` became read-only with no deprecation path

`shell/Ui/PluginBarApi.qml:26` now exposes the property as readonly, with a setter added alongside it at line 44:

```qml
property bool _centerHoverRevealSuppressed: false
readonly property bool centerHoverRevealSuppressed: _centerHoverRevealSuppressed
...
function setCenterHoverRevealSuppressed(value) { ... }
```

The first-party clock and weather panels were updated to prefer the setter (`shell/plugins/panels/clock/Panel.qml:121`):

```qml
function setCenterHoverRevealSuppressed(value) {
  if (root.bar && typeof root.bar.setCenterHoverRevealSuppressed === "function")
    root.bar.setCenterHoverRevealSuppressed(value)
  else if (root.bar && "centerHoverRevealSuppressed" in root.bar)
    root.bar.centerHoverRevealSuppressed = value
}
```

A third-party plugin written against the previous API does the direct assignment, and the natural defensive guard does not help — the property still **exists**, so `"centerHoverRevealSuppressed" in root.bar` is `true`. Only the assignment throws:

```
TypeError: Cannot assign to read-only property "centerHoverRevealSuppressed"
```

There is no load-time warning and nothing in the plugin docs marking the old form as removed, so the first signal is a runtime throw in the field.

### 2. A throw in the owner's `close()` leaves `KeyboardPanel` holding input

That call sits at the top of the panel's `close()`, so the exception aborts the function before it can hide anything:

```qml
function close() {
  setCenterHoverRevealSuppressed(false)   // throws
  root.controller.hide()                  // never runs
}
```

Every dismissal route goes through that same `close()` — `Esc` via `onCloseRequested`, outside-click via `KeyboardPanel.close()` → `owner.close()`, and re-clicking the bar icon via `toggle()`. All three die on the same line, so the panel opens but can never close.

`KeyboardPanel` is a full-screen layer-shell surface on the overlay layer whose keyboard focus follows `open` (`shell/Ui/KeyboardPanel.qml:98`):

```qml
WlrLayershell.keyboardFocus: open
  ? (focusPrimed ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive)
  : WlrKeyboardFocus.None
```

Since `open` never goes false, a screen-sized overlay keeps keyboard focus and swallows pointer input indefinitely:

```
lvl=0 ns=omarchy-background 1920x1200
lvl=2 ns=omarchy-bar        1920x35
lvl=3 ns=omarchy-keyboard-panel 1920x1200   <-- never unmaps
```

Opening still worked because `openFromHotkey()` calls `controller.show()` *before* the failing line and defers the suppression call via `Qt.callLater`, so the panel maps fine and only traps the user on the way out.

### Steps to reproduce

1. In any third-party bar-widget panel built on `KeyboardPanel`, set `root.bar.centerHoverRevealSuppressed = value` directly (the pre-4.0.3 form) as the first statement of the panel's `close()`.
2. `omarchy restart shell`, then click the widget's bar icon to open the panel.
3. Try to dismiss it — `Esc`, click outside, click the icon again.

Expected: the panel closes.
Actual: it never closes, and keyboard plus pointer input is unavailable session-wide. Recovery requires `omarchy restart shell` from a TTY or over SSH.

### Evidence

From the frozen shell process — one error per dismissal attempt, at 00:55:13, :14, :15, :16, :25, :26, :29, :31, :34, :35, :47, :48, :52, :54, :56, 00:56:00, :03, :06, until the recovery restart:

```
WARN scene: file:///home/<user>/.config/omarchy/plugins/<plugin>/Panel.qml[77:-1]:
  TypeError: Cannot assign to read-only property "centerHoverRevealSuppressed"
  (exception occurred during delayed function evaluation)
...
INFO: Exiting due to IPC request.
```

Sampling `hyprctl layers -j` once a second throughout confirmed `omarchy-keyboard-panel` stayed mapped at full screen size for the entire freeze. The compositor was never wedged — only the dismissal path was.

### Suggested hardening

The plugin-side mistake is mine to fix, and I have. But a plugin exception escalating to an unrecoverable session seems worth guarding against:

1. **Don't let an owner's `close()` failure strand the surface.** `KeyboardPanel.close()` could wrap the `owner.close()` call so that a throw still releases keyboard focus (or forces `open = false`). Currently a single bad line in third-party QML costs the user their session.
2. **Make the deprecation visible.** Either keep `centerHoverRevealSuppressed` writable and forward the write to `setCenterHoverRevealSuppressed()`, or warn once at load when a plugin holds the old expectation — anything that surfaces before a user is locked out.
3. Possibly a compositor-level escape hatch: a keybinding that force-closes any plugin-owned overlay holding exclusive/on-demand focus would make this class of bug self-recoverable.

Happy to test a patch on this machine, or to provide the full `omarchy debug` output and the raw `hyprctl layers` capture from the freeze.

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

  • Fixed bypr/11080· 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 11:20 UTC