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-5094GitHub issuePublicReceived

Third-party menu plugins receive a null appLibrary, so a cloned menu silently has no applications

Reported by kurenn · filed Sep 15, 2026Closed upstream on 2026-09-28 by kurenn; the closing comment says it was fixed by PR #13439. GitHub close reason: completed.

Received and recorded verbatim. Nothing has been checked yet.

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?
Not yet mapped to any requirement or component.
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?
Our staff decide whether the report is valid. Where this project allows it, agents decide whether it duplicates another report or a known issue. No decision has been recorded yet.

Original report

Third-party menu plugins receive a null appLibrary, so a cloned menu silently has no applicationsrevision 1 · captured 2026-10-04 12:10 UTC via poll

This report has been rewritten. The original version claimed the menu never rendered; that was a measurement error on my part, and the correction is in a comment below. The bug below is the real one, reproduced against stock Omarchy with no third-party menu code involved.

What happens

A third-party plugin that declares "kinds": ["menu"] is handed a capability-scoped shell whose appLibrary is null, while the other capabilities on the same object are populated.

shell/plugins/menu/Menu.qml opens mergeAppRows() with:

function mergeAppRows() {
  if (!root.appLibrary) return

So a third-party or cloned menu has no applications at all — nothing in the Apps submenu, nothing in app search — and nothing is logged to say why. The stock menu is unaffected, because pluginShellFor() returns the shell itself for a first-party manifest.

This matters more than it might sound, because a menu plugin that clones omarchy.menu replaces it: omarchy.menu goes into disabledPlugins, so there is no second menu still listing the user's apps.

Minimal reproduction

No third-party menu is needed. This probe plugin is enough — drop it in ~/.config/omarchy/plugins/zz.probe.applibrary/:

manifest.json:

{
  "schemaVersion": 1,
  "id": "zz.probe.applibrary",
  "name": "AppLibrary Probe",
  "version": "1.0.0",
  "kinds": ["menu"],
  "keepLoaded": true,
  "entryPoints": { "menu": "Menu.qml" }
}

Menu.qml:

import QtQuick

Item {
  id: root
  property string omarchyPath: ""
  property var shell: null
  property var manifest: null
  property bool opened: false

  function open(payloadJson) { root.opened = true }
  function close() { root.opened = false }

  onShellChanged: {
    console.warn("PROBE shell=" + (root.shell ? "yes" : "null")
      + " pluginId=" + (root.shell ? root.shell.pluginId : "-")
      + " appLibrary=" + (root.shell && root.shell.appLibrary ? "PRESENT" : "NULL")
      + " bar=" + (root.shell && root.shell.bar ? "PRESENT" : "NULL")
      + " barConfig=" + (root.shell && root.shell.barConfig ? "PRESENT" : "NULL"))
  }
}

Then omarchy plugin enable zz.probe.applibrary and omarchy restart shell. The log shows:

WARN qml: PROBE shell=yes pluginId=zz.probe.applibrary appLibrary=NULL bar=PRESENT barConfig=PRESENT

pluginId, bar, and barConfig all arrive correctly on the same object, so the scoped API is constructed and alive. Only appLibrary is null.

Why this looks unintended

The plumbing to provide it is all present and appears to be meant to fire for exactly this case:

  • services/PluginAppLibraryApi.qml exists as a detached app-library capability, documented as being "for third-party menus"
  • pluginAppLibraryFor(cacheKey, pluginId) builds one
  • createScopedPluginShell() passes it in conditionally:
  appLibrary: shell.manifestHasKind(manifest, "menu")
    ? shell.pluginAppLibraryFor(cacheKey, key) : null,
  • pluginShellCapabilityProfile() even carries a "menu" / "no-menu" dimension so the cached facade is rebuilt when that changes

I could not work out from reading the source why the result is null for a manifest whose kinds contains menu. computePanelEntries() has already matched the same manifest on the same array to give the plugin its menu entry point, so manifestHasKind(manifest, "menu") should be true by then. No QML error is logged at creation. I'm reporting the reproduction rather than guessing at a patch.

Corroboration

The author of omarchy-menu-calculator-plugin hit this independently and shipped a workaround in 2ffacdd, rebuilding the capability inside the plugin from DesktopEntries plus the shell's own AppSearch.js, hidden-entries.sh, and launcher.hides. Their swap is conditional on the host providing nothing, so a fix here takes the capability back automatically.

Secondary ask

Whatever the cause, mergeAppRows() returning silently is what made this expensive to track down. A menu with no application library is never intentional, so a single warning naming the plugin would turn "my apps disappeared" into something searchable.

System details

Omarchy4.0.3-1 (stable)
Kernel7.2.3-arch1-3
CPUAMD Ryzen 7 5825U with Radeon Graphics
GPUAMD/ATI Barcelo (rev c1)
Hyprlandv0.56.2
quickshell0.3.1-1
View raw text
**This report has been rewritten. The original version claimed the menu never rendered; that was a measurement error on my part, and the correction is in a comment below. The bug below is the real one, reproduced against stock Omarchy with no third-party menu code involved.**

## What happens

A third-party plugin that declares `"kinds": ["menu"]` is handed a capability-scoped `shell` whose `appLibrary` is `null`, while the other capabilities on the same object are populated.

`shell/plugins/menu/Menu.qml` opens `mergeAppRows()` with:

```qml
function mergeAppRows() {
  if (!root.appLibrary) return
```

So a third-party or cloned menu has **no applications at all** — nothing in the Apps submenu, nothing in app search — and nothing is logged to say why. The stock menu is unaffected, because `pluginShellFor()` returns the shell itself for a first-party manifest.

This matters more than it might sound, because a menu plugin that clones `omarchy.menu` *replaces* it: `omarchy.menu` goes into `disabledPlugins`, so there is no second menu still listing the user's apps.

## Minimal reproduction

No third-party menu is needed. This probe plugin is enough — drop it in `~/.config/omarchy/plugins/zz.probe.applibrary/`:

`manifest.json`:

```json
{
  "schemaVersion": 1,
  "id": "zz.probe.applibrary",
  "name": "AppLibrary Probe",
  "version": "1.0.0",
  "kinds": ["menu"],
  "keepLoaded": true,
  "entryPoints": { "menu": "Menu.qml" }
}
```

`Menu.qml`:

```qml
import QtQuick

Item {
  id: root
  property string omarchyPath: ""
  property var shell: null
  property var manifest: null
  property bool opened: false

  function open(payloadJson) { root.opened = true }
  function close() { root.opened = false }

  onShellChanged: {
    console.warn("PROBE shell=" + (root.shell ? "yes" : "null")
      + " pluginId=" + (root.shell ? root.shell.pluginId : "-")
      + " appLibrary=" + (root.shell && root.shell.appLibrary ? "PRESENT" : "NULL")
      + " bar=" + (root.shell && root.shell.bar ? "PRESENT" : "NULL")
      + " barConfig=" + (root.shell && root.shell.barConfig ? "PRESENT" : "NULL"))
  }
}
```

Then `omarchy plugin enable zz.probe.applibrary` and `omarchy restart shell`. The log shows:

```
WARN qml: PROBE shell=yes pluginId=zz.probe.applibrary appLibrary=NULL bar=PRESENT barConfig=PRESENT
```

`pluginId`, `bar`, and `barConfig` all arrive correctly on the same object, so the scoped API is constructed and alive. Only `appLibrary` is null.

## Why this looks unintended

The plumbing to provide it is all present and appears to be meant to fire for exactly this case:

- `services/PluginAppLibraryApi.qml` exists as a detached app-library capability, documented as being "for third-party menus"
- `pluginAppLibraryFor(cacheKey, pluginId)` builds one
- `createScopedPluginShell()` passes it in conditionally:
  ```qml
  appLibrary: shell.manifestHasKind(manifest, "menu")
    ? shell.pluginAppLibraryFor(cacheKey, key) : null,
  ```
- `pluginShellCapabilityProfile()` even carries a `"menu"` / `"no-menu"` dimension so the cached facade is rebuilt when that changes

I could not work out from reading the source why the result is null for a manifest whose `kinds` contains `menu`. `computePanelEntries()` has already matched the same manifest on the same array to give the plugin its `menu` entry point, so `manifestHasKind(manifest, "menu")` should be true by then. No QML error is logged at creation. I'm reporting the reproduction rather than guessing at a patch.

## Corroboration

The author of [omarchy-menu-calculator-plugin](https://github.com/koenhendriks/omarchy-menu-calculator-plugin) hit this independently and shipped a workaround in `2ffacdd`, rebuilding the capability inside the plugin from `DesktopEntries` plus the shell's own `AppSearch.js`, `hidden-entries.sh`, and `launcher.hides`. Their swap is conditional on the host providing nothing, so a fix here takes the capability back automatically.

## Secondary ask

Whatever the cause, `mergeAppRows()` returning silently is what made this expensive to track down. A menu with no application library is never intentional, so a single warning naming the plugin would turn "my apps disappeared" into something searchable.

## System details

| | |
|---|---|
| Omarchy | 4.0.3-1 (stable) |
| Kernel | 7.2.3-arch1-3 |
| CPU | AMD Ryzen 7 5825U with Radeon Graphics |
| GPU | AMD/ATI Barcelo (rev c1) |
| Hyprland | v0.56.2 |
| quickshell | 0.3.1-1 |

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.

No decision has been recorded yet.