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-5279GitHub issuePublicFixed

Cloned menu plugins get appLibrary: null, so the Apps submenu is empty (4.0.4)

Reported by raming8080 · filed Oct 9, 2026 · open at source

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

R-5209 is the earliest report of this defect — filed Oct 7, 2026, open upstream. This report (filed Oct 9, 2026) describes the same defect, so it is folded under it in the inbox.

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

Cloned menu plugins get appLibrary: null, so the Apps submenu is empty (4.0.4)revision 1 · captured 2026-10-09 18:48 UTC via poll

What happened

After updating 4.0.2 → 4.0.4, the Apps submenu of a cloned menu plugin (omarchy plugin clone omarchy.menu → jr.menu) shows "Nothing here yet" for every application. Searching for an app name from the root menu finds only non-app rows (e.g. Setup › Defaults › Browser). Newly installed apps (Google Chrome in my case) therefore never appear in the menu.

The stock omarchy.menu lists apps normally. Disabling the clone and enabling the stock plugin fixes it immediately, with the clone's Menu.qml otherwise byte-identical to the shipped one apart from an unrelated keybinding tweak.

Expected

A cloned menu plugin should list applications exactly like the stock plugin, since cloning is the supported way to customise it.

Steps to reproduce

  1. omarchy plugin clone omarchy.menu (no edits needed), make sure the clone is the enabled menu.
  2. omarchy-restart-shell
  3. Open the menu → Apps. It shows "Nothing here yet".
  4. omarchy-shell shell setPluginEnabled <clone-id> false; omarchy-shell shell setPluginEnabled omarchy.menu true → Apps is populated again.

Root cause

Adding console.log to the clone shows root.shell is a PluginShellApi whose appLibrary is null from the moment it is assigned (not destroyed later).

In shell/shell.qml:

  • pluginShellFor(manifest) returns the real shell for first-party plugins, but for third-party plugins calls createScopedPluginShell(manifest, …), which sets

appLibrary: shell.manifestHasKind(manifest, "menu") ? shell.pluginAppLibraryFor(cacheKey, key) : null.
- manifestHasKind() is Array.isArray(manifest.kinds) && manifest.kinds.indexOf(kind) !== -1.
- Panel/overlay/menu plugins are loaded by an Instantiator over shell.panelEntries, and the delegate passes panelEntry.manifest (i.e. modelData.manifest) into pluginShellFor().

Going through the Instantiator's modelData converts the nested kinds array into a QVariantList, which QML exposes as a V4Sequence, not a JS Array. So Array.isArray(manifest.kinds) is false, manifestHasKind() returns false, and the scoped shell gets appLibrary: null. indexOf still works on the sequence, which is why computePanelEntries() and isEnabled() are unaffected and the plugin still loads — only the Array.isArray guard breaks.

Minimal reproduction, run with quickshell -p:

import QtQuick
import Quickshell
ShellRoot {
  id: shell
  property var entries: [{ id: "x", manifest: { kinds: ["menu", "bar-widget"] } }]
  function hasKind(m, k) { return !!m && Array.isArray(m.kinds) && m.kinds.indexOf(k) !== -1 }
  Instantiator {
    model: shell.entries
    delegate: QtObject {
      required property var modelData
      Component.onCompleted: console.log(
        "direct=" + Array.isArray(shell.entries[0].manifest.kinds)
        + " viaModelData=" + Array.isArray(modelData.manifest.kinds)
        + " hasKind=" + shell.hasKind(modelData.manifest, "menu")
        + " type=" + Object.prototype.toString.call(modelData.manifest.kinds))
    }
  }
}

Output on this system:

direct=true viaModelData=false hasKind=false type=[object V4Sequence]

First-party plugins never hit this because pluginShellFor() short-circuits to the host shell before the kind check.

Suggested fix

Either look the manifest up fresh in the Instantiator delegate instead of using modelData:

if ("shell" in item) item.shell = shell.pluginShellFor(shell.pluginRegistry.installedPlugins[panelEntry.pluginId])

or make manifestHasKind() tolerant of sequences:

function manifestHasKind(manifest, kind) {
  var kinds = manifest && manifest.kinds
  return !!kinds && typeof kinds.indexOf === "function" && kinds.indexOf(kind) !== -1
}

(createScopedPluginShell and pluginShellCapabilityProfile call manifestHasKind with the same object, so the profile cache key is consistent either way.)

Environment

  • omarchy version: 4.0.4-1 (also present in current master shell/shell.qml, manifestHasKind at ~line 354 and the panelEntry.manifest hand-off at ~line 1384)
  • quickshell 0.3.1-1, qt6-declarative 6.11.2-1, hyprland 0.56.2-2
  • Kernel 7.2.5-3-omarchy, Dell Latitude 7450, x86_64

Workaround until fixed: in the clone's Menu.qml, import qs.services, declare property AppLibrary localAppLibrary: AppLibrary { } and make appLibrary fall back to it when root.shell.appLibrary is null.

Filed by Claude Fable 5.1 via Claude Code.

View raw text
## What happened

After updating 4.0.2 → 4.0.4, the **Apps** submenu of a cloned menu plugin (`omarchy plugin clone omarchy.menu` → `jr.menu`) shows "Nothing here yet" for every application. Searching for an app name from the root menu finds only non-app rows (e.g. Setup › Defaults › Browser). Newly installed apps (Google Chrome in my case) therefore never appear in the menu.

The stock `omarchy.menu` lists apps normally. Disabling the clone and enabling the stock plugin fixes it immediately, with the clone's `Menu.qml` otherwise byte-identical to the shipped one apart from an unrelated keybinding tweak.

## Expected

A cloned menu plugin should list applications exactly like the stock plugin, since cloning is the supported way to customise it.

## Steps to reproduce

1. `omarchy plugin clone omarchy.menu` (no edits needed), make sure the clone is the enabled menu.
2. `omarchy-restart-shell`
3. Open the menu → **Apps**. It shows "Nothing here yet".
4. `omarchy-shell shell setPluginEnabled <clone-id> false; omarchy-shell shell setPluginEnabled omarchy.menu true` → Apps is populated again.

## Root cause

Adding `console.log` to the clone shows `root.shell` is a `PluginShellApi` whose `appLibrary` is **null from the moment it is assigned** (not destroyed later).

In `shell/shell.qml`:

- `pluginShellFor(manifest)` returns the real shell for first-party plugins, but for third-party plugins calls `createScopedPluginShell(manifest, …)`, which sets
  `appLibrary: shell.manifestHasKind(manifest, "menu") ? shell.pluginAppLibraryFor(cacheKey, key) : null`.
- `manifestHasKind()` is `Array.isArray(manifest.kinds) && manifest.kinds.indexOf(kind) !== -1`.
- Panel/overlay/menu plugins are loaded by an `Instantiator` over `shell.panelEntries`, and the delegate passes `panelEntry.manifest` (i.e. `modelData.manifest`) into `pluginShellFor()`.

Going through the Instantiator's `modelData` converts the nested `kinds` array into a `QVariantList`, which QML exposes as a `V4Sequence`, **not** a JS Array. So `Array.isArray(manifest.kinds)` is `false`, `manifestHasKind()` returns `false`, and the scoped shell gets `appLibrary: null`. `indexOf` still works on the sequence, which is why `computePanelEntries()` and `isEnabled()` are unaffected and the plugin still loads — only the `Array.isArray` guard breaks.

Minimal reproduction, run with `quickshell -p`:

```qml
import QtQuick
import Quickshell
ShellRoot {
  id: shell
  property var entries: [{ id: "x", manifest: { kinds: ["menu", "bar-widget"] } }]
  function hasKind(m, k) { return !!m && Array.isArray(m.kinds) && m.kinds.indexOf(k) !== -1 }
  Instantiator {
    model: shell.entries
    delegate: QtObject {
      required property var modelData
      Component.onCompleted: console.log(
        "direct=" + Array.isArray(shell.entries[0].manifest.kinds)
        + " viaModelData=" + Array.isArray(modelData.manifest.kinds)
        + " hasKind=" + shell.hasKind(modelData.manifest, "menu")
        + " type=" + Object.prototype.toString.call(modelData.manifest.kinds))
    }
  }
}
```

Output on this system:

```
direct=true viaModelData=false hasKind=false type=[object V4Sequence]
```

First-party plugins never hit this because `pluginShellFor()` short-circuits to the host shell before the kind check.

## Suggested fix

Either look the manifest up fresh in the Instantiator delegate instead of using `modelData`:

```qml
if ("shell" in item) item.shell = shell.pluginShellFor(shell.pluginRegistry.installedPlugins[panelEntry.pluginId])
```

or make `manifestHasKind()` tolerant of sequences:

```qml
function manifestHasKind(manifest, kind) {
  var kinds = manifest && manifest.kinds
  return !!kinds && typeof kinds.indexOf === "function" && kinds.indexOf(kind) !== -1
}
```

(`createScopedPluginShell` and `pluginShellCapabilityProfile` call `manifestHasKind` with the same object, so the profile cache key is consistent either way.)

## Environment

- `omarchy version`: 4.0.4-1 (also present in current `master` `shell/shell.qml`, `manifestHasKind` at ~line 354 and the `panelEntry.manifest` hand-off at ~line 1384)
- quickshell 0.3.1-1, qt6-declarative 6.11.2-1, hyprland 0.56.2-2
- Kernel 7.2.5-3-omarchy, Dell Latitude 7450, x86_64

Workaround until fixed: in the clone's `Menu.qml`, `import qs.services`, declare `property AppLibrary localAppLibrary: AppLibrary { }` and make `appLibrary` fall back to it when `root.shell.appLibrary` is null.

Filed by Claude Fable 5.1 via Claude Code.

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-5209Decided by agents · verified · 2026-10-09 18:55 UTC