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.
Plugin hot-reload while locked destroys the lock client; Quickshell then SIGABRTs
Reported by oliverlukschander · filed Aug 31, 2026 · open at source
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?
- 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
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 lockSignal: 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=falseleaves 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:
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 lockedStack (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
- Lock the session (idle timeout or
omarchy system lock) and wait until the lock issecure. - Save/touch a file in an enabled plugin under
~/.config/omarchy/plugins/(or let a background agent do so). - Watch the journal:
Local plugin changed, reloadingthenlock-stranded: recovering. - 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/versionstill 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
- Duplicate ofR-1075Quickshell SIGABRT ("Tried to show lockscreen surfaces without active lock") when a plugin live-reloads while the screen is locked· decided by agents · verified
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.