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.
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.
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
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) returnSo 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=PRESENTpluginId, 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.qmlexists as a detached app-library capability, documented as being "for third-party menus"pluginAppLibraryFor(cacheKey, pluginId)builds onecreateScopedPluginShell()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
| 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 |
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.