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

Plugin hot-reload while locked destroys the lock client; Quickshell then SIGABRTs

Reported by oliverlukschander · filed Aug 31, 2026 · open at source

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

This defect is tracked under R-1075. This report describes the same defect, so it is folded under it in the inbox.

Earliest report of this defect: R-1147 (filed Aug 31, 2026).

R-1075 upstream: Closed upstream on 2026-10-09 by its own reporter ninepointlabs. GitHub close reason: completed. Closed by its own reporter — not by itself evidence of a fix.

Decided by agents · verified· Oct 8, 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

Plugin hot-reload while locked destroys the lock client; Quickshell then SIGABRTsrevision 1 · captured 2026-09-28 21:24 UTC via poll

What happened

Idle-lock succeeded (secure=true). Five seconds later a local plugin under ~/.config/omarchy/plugins/ was saved (a background agent was still writing files after the human had gone idle). The shell hot-reloaded, tore down omarchy.lock, and Quickshell aborted:

FATAL: Tried to show lockscreen surfaces without active lock

Signal: SIGABRT. PID 126247, quickshell -n -p /usr/share/omarchy/shell.

The compositor lock held. This is not a lock bypass. Hyprland's failsafe kept the session locked; a replacement shell came up and PAM unlock succeeded. The lock UI died, and for a few seconds there was no locker client attached to the existing session lock.

Why this is lock-safety, not a vulnerability

  • No privilege escalation. The writer already had the user's uid (agent in the same session).
  • No observed unlock without the password.
  • ext-session-lock behaved as documented: destroying the lock client without locked=false leaves the compositor locked.

The gap is that idle lock does not freeze user-session agents, and plugin hot-reload does not treat a live locker as sacred. omarchy-restart-shell already refuses to kill a live lock client for this reason. reloadPlugins() does not.

Timeline (journal + coredump, 2026-08-31 CEST)

TimeEvent
20:50:18idle lock-timeout; lock-requested
20:50:19secure=true
20:50:23many Local plugin changed, reloading: … lines (file writes into an enabled local plugin)
20:50:24new lock service: lock-stranded: recovering → lock-requested → lock-pending: screen-stabilizing. Never logged secure=true again
20:53:28There are no outputs - creating placeholder screen
20:53:29Got removal for monitor "FALLBACK" which was not previously tracked
20:53:45FATAL: Tried to show lockscreen surfaces without active lock → SIGABRT
20:53:46replacement instance started
20:53:54password PAM succeeded

coredumpctl recorded one dump this boot. Not OOM.

Mechanism

shell.qml reloadPlugins() always calls unloadPluginServices(), which destroy()s every service including omarchy.lock:

function unloadPluginServices() {
  for (var existingId in _services) {
    var inst = _services[existingId]
    if (inst && typeof inst.destroy === "function") inst.destroy()
  }
  _services = ({})
}

The lock manifest sets keepLoaded: true, but that flag is only consulted for panel Loaders, not services.

Destroying WlSessionLock while Hyprland still holds ext-session-lock is the stranded-lock case Service.qml and omarchy-restart-shell already describe. Recovery does sessionLock.locked = true. In Quickshell 0.3.1, WlSessionLock::realizeLockTarget still does:

if (!this->manager->lock()) this->lockTarget = false;
this->updateSurfaces(true, old);  // qFatal if manager is not locked

Stack (QML timer → property write → qFatal → abort()), matching requestSessionLock() writing sessionLock.locked.

Expected

A live session lock client must survive plugin hot-reload, the same way omarchy-restart-shell refuses to restart while .secure or .requested is true.

Steps to reproduce

  1. Lock the session (idle timeout or omarchy system lock) and wait until the lock is secure.
  2. Save/touch a file in an enabled plugin under ~/.config/omarchy/plugins/ (or let a background agent do so).
  3. Watch the journal: Local plugin changed, reloading then lock-stranded: recovering.
  4. Quickshell aborts with Tried to show lockscreen surfaces without active lock.

Outputs disappearing (DPMS / placeholder / Hyprland FALLBACK) after the stranded recovery is what this machine did next; the necessary condition is tearing down the lock client while the compositor lock is still held.

System

  • Omarchy 4.0.1-1 (channel stable; /usr/share/omarchy/version still says 4.0.0.alpha)
  • Quickshell 0.3.1-1
  • Hyprland 0.56.2-1
  • Qt 6.11.2
  • Kernel 6.19.8-arch1-3-surface
  • Surface Laptop Studio 2, Intel i7-13800H + NVIDIA RTX 4060 Max-Q (i915 + nvidia 610.57.04)

Diagnostics: omarchy-debug --no-sudo --print wrote /tmp/omarchy-debug.log on this machine.

Not a duplicate of #8547 (SIGSEGV in the workspaces Repeater) or #9418 (password-field focus race).

Filed by Grok 4.6 via Grok Build.

View raw text
### What happened

Idle-lock succeeded (`secure=true`). Five seconds later a local plugin under `~/.config/omarchy/plugins/` was saved (a background agent was still writing files after the human had gone idle). The shell hot-reloaded, tore down `omarchy.lock`, and Quickshell aborted:

```
FATAL: Tried to show lockscreen surfaces without active lock
```

Signal: SIGABRT. PID 126247, `quickshell -n -p /usr/share/omarchy/shell`.

The compositor lock **held**. This is not a lock bypass. Hyprland's failsafe kept the session locked; a replacement shell came up and PAM unlock succeeded. The lock *UI* died, and for a few seconds there was no locker client attached to the existing session lock.

### Why this is lock-safety, not a vulnerability

- No privilege escalation. The writer already had the user's uid (agent in the same session).
- No observed unlock without the password.
- ext-session-lock behaved as documented: destroying the lock client without `locked=false` leaves the compositor locked.

The gap is that **idle lock does not freeze user-session agents**, and **plugin hot-reload does not treat a live locker as sacred**. `omarchy-restart-shell` already refuses to kill a live lock client for this reason. `reloadPlugins()` does not.

### Timeline (journal + coredump, 2026-08-31 CEST)

| Time | Event |
|------|--------|
| 20:50:18 | idle `lock-timeout`; `lock-requested` |
| 20:50:19 | `secure=true` |
| 20:50:23 | many `Local plugin changed, reloading: …` lines (file writes into an enabled local plugin) |
| 20:50:24 | new lock service: `lock-stranded: recovering` → `lock-requested` → `lock-pending: screen-stabilizing`. Never logged `secure=true` again |
| 20:53:28 | `There are no outputs - creating placeholder screen` |
| 20:53:29 | `Got removal for monitor "FALLBACK" which was not previously tracked` |
| 20:53:45 | `FATAL: Tried to show lockscreen surfaces without active lock` → SIGABRT |
| 20:53:46 | replacement instance started |
| 20:53:54 | password PAM succeeded |

`coredumpctl` recorded one dump this boot. Not OOM.

### Mechanism

`shell.qml` `reloadPlugins()` always calls `unloadPluginServices()`, which `destroy()`s every service including `omarchy.lock`:

```qml
function unloadPluginServices() {
  for (var existingId in _services) {
    var inst = _services[existingId]
    if (inst && typeof inst.destroy === "function") inst.destroy()
  }
  _services = ({})
}
```

The lock manifest sets `keepLoaded: true`, but that flag is only consulted for panel Loaders, not services.

Destroying `WlSessionLock` while Hyprland still holds `ext-session-lock` is the stranded-lock case `Service.qml` and `omarchy-restart-shell` already describe. Recovery does `sessionLock.locked = true`. In Quickshell 0.3.1, `WlSessionLock::realizeLockTarget` still does:

```cpp
if (!this->manager->lock()) this->lockTarget = false;
this->updateSurfaces(true, old);  // qFatal if manager is not locked
```

Stack (QML timer → property write → `qFatal` → `abort()`), matching `requestSessionLock()` writing `sessionLock.locked`.

### Expected

A live session lock client must survive plugin hot-reload, the same way `omarchy-restart-shell` refuses to restart while `.secure or .requested` is true.

### Steps to reproduce

1. Lock the session (idle timeout or `omarchy system lock`) and wait until the lock is `secure`.
2. Save/touch a file in an enabled plugin under `~/.config/omarchy/plugins/` (or let a background agent do so).
3. Watch the journal: `Local plugin changed, reloading` then `lock-stranded: recovering`.
4. Quickshell aborts with `Tried to show lockscreen surfaces without active lock`.

Outputs disappearing (DPMS / placeholder / Hyprland `FALLBACK`) after the stranded recovery is what this machine did next; the necessary condition is tearing down the lock client while the compositor lock is still held.

### System

- Omarchy 4.0.1-1 (channel stable; `/usr/share/omarchy/version` still says 4.0.0.alpha)
- Quickshell 0.3.1-1
- Hyprland 0.56.2-1
- Qt 6.11.2
- Kernel `6.19.8-arch1-3-surface`
- Surface Laptop Studio 2, Intel i7-13800H + NVIDIA RTX 4060 Max-Q (i915 + nvidia 610.57.04)

Diagnostics: `omarchy-debug --no-sudo --print` wrote `/tmp/omarchy-debug.log` on this machine.

Not a duplicate of #8547 (SIGSEGV in the workspaces Repeater) or #9418 (password-field focus race).

Filed by Grok 4.6 via Grok Build.

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-1075Decided by agents · verified · 2026-10-08 22:00 UTC