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

Shell reload loop: PluginRegistry watches pluginsDir recursively without filtering, so plugin state writes tear down the whole bar

Reported by maTzko13 · filed Sep 16, 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

Shell reload loop: PluginRegistry watches pluginsDir recursively without filtering, so plugin state writes tear down the whole barrevision 1 · captured 2026-09-28 21:23 UTC via poll

Description

PluginRegistry.qml watches the whole plugin tree with an unfiltered recursive inotifywait, so any file a plugin writes inside its own directory triggers a full shell reload. A third-party plugin that keeps runtime state next to its QML can therefore make the entire bar unusable.

services/PluginRegistry.qml:663-673:

property Process localPluginWatcher: Process {
  command: [
    "inotifywait", "-m", "-r", "-q",
    "-e", "close_write,create,delete,move",
    "--format", "%w%f",
    registry.pluginsDir
  ]

Every event reaches shell.qml:1474 → reloadPlugins(), which calls unloadPanels(), unloadPluginServices() and unloadPluginWidgets(). The complete bar is torn down and rebuilt, and any open panel or overlay is destroyed along with it — including widgets from unrelated plugins.

Steps to reproduce

  1. Install a plugin that writes runtime state into its own plugin directory. I hit this with icyleaf.bitwarden, whose helper binary keeps a 7.5 MB vault cache at ~/.config/omarchy/plugins/<id>/data.json and rewrites it via atomic rename on every sync (→ move event).
  2. Watch the reload counter while the shell is otherwise idle:
journalctl --user --since "30 sec ago" | grep -c "Local plugin changed"

Measured on my machine: 243 reloads in 10 minutes, 56 of them within 30 seconds while the plugin's own window was closed. The bar visibly rebuilt roughly twice per second; unrelated widgets (ytmusic, syncthing, notification center) were torn down on every cycle.

Expected

A plugin writing its own data files should not force a full shell reload. Only changes that actually affect loadable plugin code or metadata should.

Actual

Every close_write/create/delete/move anywhere under pluginsDir — caches, logs, downloaded binaries, config — triggers a complete teardown and rebuild of the bar.

Notes

This is easy to misread as a crash. The Quickshell PID stays stable throughout and coredumpctl is empty; only the journal line Local plugin changed, reloading: <id> reveals what is happening. That cost me a while to pin down, so it may be worth surfacing more clearly.

Possible directions, in rough order of how much they'd help:

  • Filter the watch to the files that matter (manifest.json, *.qml, *.js), e.g. via inotifywait --include, or filter in localPluginIdForPath.
  • Debounce or rate-limit reloads per plugin id — localPluginReloadTimer already exists but each event restarts it, so a steady stream of writes keeps the shell reloading indefinitely.
  • Document a convention that plugins keep mutable state outside pluginsDir (e.g. under ~/.local/share/), so the watch stays a reasonable assumption.

Happy to open a PR for the filtering approach if you'd like it in a particular shape.

System

Omarchy4.0.3-1
Hyprland0.56.2
quickshell0.3.1-1
inotify-tools4.25.9.0-1
Kernel7.2.3-arch1-3
View raw text
### Description

`PluginRegistry.qml` watches the whole plugin tree with an unfiltered recursive `inotifywait`, so **any** file a plugin writes inside its own directory triggers a full shell reload. A third-party plugin that keeps runtime state next to its QML can therefore make the entire bar unusable.

`services/PluginRegistry.qml:663-673`:

```qml
property Process localPluginWatcher: Process {
  command: [
    "inotifywait", "-m", "-r", "-q",
    "-e", "close_write,create,delete,move",
    "--format", "%w%f",
    registry.pluginsDir
  ]
```

Every event reaches `shell.qml:1474` → `reloadPlugins()`, which calls `unloadPanels()`, `unloadPluginServices()` and `unloadPluginWidgets()`. The complete bar is torn down and rebuilt, and any open panel or overlay is destroyed along with it — including widgets from unrelated plugins.

### Steps to reproduce

1. Install a plugin that writes runtime state into its own plugin directory. I hit this with `icyleaf.bitwarden`, whose helper binary keeps a 7.5 MB vault cache at `~/.config/omarchy/plugins/<id>/data.json` and rewrites it via atomic `rename` on every sync (→ `move` event).
2. Watch the reload counter while the shell is otherwise idle:

```bash
journalctl --user --since "30 sec ago" | grep -c "Local plugin changed"
```

**Measured on my machine:** 243 reloads in 10 minutes, 56 of them within 30 seconds **while the plugin's own window was closed**. The bar visibly rebuilt roughly twice per second; unrelated widgets (ytmusic, syncthing, notification center) were torn down on every cycle.

### Expected

A plugin writing its own data files should not force a full shell reload. Only changes that actually affect loadable plugin code or metadata should.

### Actual

Every `close_write`/`create`/`delete`/`move` anywhere under `pluginsDir` — caches, logs, downloaded binaries, config — triggers a complete teardown and rebuild of the bar.

### Notes

This is easy to misread as a crash. The Quickshell PID stays stable throughout and `coredumpctl` is empty; only the journal line `Local plugin changed, reloading: <id>` reveals what is happening. That cost me a while to pin down, so it may be worth surfacing more clearly.

Possible directions, in rough order of how much they'd help:

- Filter the watch to the files that matter (`manifest.json`, `*.qml`, `*.js`), e.g. via `inotifywait --include`, or filter in `localPluginIdForPath`.
- Debounce or rate-limit reloads per plugin id — `localPluginReloadTimer` already exists but each event restarts it, so a steady stream of writes keeps the shell reloading indefinitely.
- Document a convention that plugins keep mutable state outside `pluginsDir` (e.g. under `~/.local/share/`), so the watch stays a reasonable assumption.

Happy to open a PR for the filtering approach if you'd like it in a particular shape.

### System

| | |
|---|---|
| Omarchy | 4.0.3-1 |
| Hyprland | 0.56.2 |
| quickshell | 0.3.1-1 |
| inotify-tools | 4.25.9.0-1 |
| Kernel | 7.2.3-arch1-3 |

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

Not linked to any known issue, other report or change yet.

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 14:23 UTC