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.
4.0.3: third-party plugins get appLibrary=null and a manifest stripped of __sourceDir, both silently
Reported by trustimagetiago · filed Sep 9, 2026Closed upstream on 2026-09-20 by bjarneo via an automated message, as a duplicate of #10661 (R-860). 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
Summary
Omarchy 4.0.3 tightened the API handed to third-party plugins. Two members that plugins depend on are now unreachable, and both failures are silent — no QML warning, no error, just an empty or dead widget. First-party plugins are unaffected because they receive the real shell objects, so the stock UI looks fine while every clone/fork breaks.
Both were bisected on a machine upgraded 4.0.1 → 4.0.3; both worked on 4.0.1.
Bug 1 — shell.appLibrary is null for third-party menu plugins
A plugin with "kinds": ["menu"] gets shell.appLibrary === null, so Menu.qml's mergeAppRows() returns at its first guard and the Apps submenu renders "Nothing here yet" forever.
shell.qml:603 looks correct:
appLibrary: shell.manifestHasKind(manifest, "menu")
? shell.pluginAppLibraryFor(cacheKey, key) : null,and the manifest does reach the plugin with the right kinds — logged from inside the running plugin:
DIAG manifest={... "id":"<id>", "kinds":["menu","bar-widget"] ...}
DIAG shell.pluginId=<id>
DIAG shell.appLibrary direto=nullSo manifestHasKind is satisfied and pluginAppLibraryFor() still yields nothing usable by the time the plugin reads it. PluginAppLibraryApi instances are created with createObject(null, {...}) and only referenced from JS-side caches — worth checking whether they survive collection.
The app data itself is fine: DesktopEntries.applications.values holds 73 entries, and AppSearch.sortedEntries() returns all 73. Only the delivery to the plugin is broken.
Reproduce
mkdir -p ~/.config/omarchy/plugins/repro.menutest
cp /usr/share/omarchy/shell/plugins/menu/{Menu.qml,MenuModel.js} \
~/.config/omarchy/plugins/repro.menutest/
cat > ~/.config/omarchy/plugins/repro.menutest/manifest.json <<'JSON'
{
"schemaVersion": 1,
"id": "repro.menutest",
"name": "Repro menu",
"version": "1.0.0",
"author": "repro",
"description": "Unmodified copy of omarchy.menu",
"kinds": ["menu"],
"keepLoaded": true,
"entryPoints": { "menu": "Menu.qml" }
}
JSONAdd {"id": "repro.menutest"} to plugins in ~/.config/omarchy/shell.json, restart the shell, then:
omarchy-shell shell toggle repro.menutest '{"menu":"apps"}'Expected: the installed applications, as omarchy.menu shows them.
Actual: "Nothing here yet".
The files are byte-identical to the shipped plugin — only the manifest id differs. Adding one console.log to mergeAppRows() in that copy prints:
REPRO appLibrary=null shell=PluginShellApi_QMLTYPE_26(0x...)Bug 2 — __sourceDir is stripped, so third-party plugins cannot find their own bundled files
publicPluginManifest() deletes __sourceDir for anything not first-party (shell.qml:318-322):
function publicPluginManifest(manifest) {
if (!manifest) return null
if (manifest.__isFirstParty) return manifest
var copy = JSON.parse(JSON.stringify(manifest))
delete copy.__sourceDir
...Any third-party plugin shipping a helper script or asset alongside its QML uses manifest.__sourceDir to locate it. With the field gone the path resolves empty and the plugin runs bash "", which fails silently.
Concretely, the third-party Workload plugin resolves its collector as:
readonly property string sourceDir: manifest && manifest.__sourceDir
? String(manifest.__sourceDir).replace(/^file:\/\//, "").replace(/\/$/, "") : ""
readonly property string collector: sourceDir ? sourceDir + "/workload-collector" : ""On 4.0.3 every reading renders as zero — 0 °C, 0 MHz, 0 threads, 0.0/0.0 GB memory, 0/0 GB disk — while running the same script by hand returns a full JSON snapshot immediately. No error is logged.
sourceDir is still put into bar-widget registry metadata (shell.qml:1410), but PluginBarWidgetRegistryApi does not expose it, so there is no supported way for a third-party plugin to find its own directory. Qt.resolvedUrl(".") works as a local workaround, but plugins that follow the documented __sourceDir contract all break on upgrade.
Suggested direction
If restricting these is deliberate, the replacements need to be reachable and the removals announced — a third-party plugin currently has no supported way to (a) list applications or (b) locate its own bundled files, and both fail without a diagnostic. If it is not deliberate, appLibrary delivery and __sourceDir (or an exposed equivalent on the registry API) should be restored for third-party plugins.
A warning when a plugin reads a stripped/nulled member would have turned hours of bisecting into one log line.
System
- Omarchy 4.0.3-1 (regressed from 4.0.1-1)
- omarchy-settings 4.0.3-1
- quickshell 0.3.1-1
- qt6-base 6.11.2-3
- Arch Linux, Hyprland, x86_64
View raw text
## Summary
Omarchy 4.0.3 tightened the API handed to **third-party** plugins. Two members that plugins depend on are now unreachable, and both failures are silent — no QML warning, no error, just an empty or dead widget. First-party plugins are unaffected because they receive the real shell objects, so the stock UI looks fine while every clone/fork breaks.
Both were bisected on a machine upgraded 4.0.1 → 4.0.3; both worked on 4.0.1.
---
## Bug 1 — `shell.appLibrary` is `null` for third-party menu plugins
A plugin with `"kinds": ["menu"]` gets `shell.appLibrary === null`, so `Menu.qml`'s `mergeAppRows()` returns at its first guard and the **Apps** submenu renders "Nothing here yet" forever.
`shell.qml:603` looks correct:
```qml
appLibrary: shell.manifestHasKind(manifest, "menu")
? shell.pluginAppLibraryFor(cacheKey, key) : null,
```
and the manifest does reach the plugin with the right kinds — logged from inside the running plugin:
```
DIAG manifest={... "id":"<id>", "kinds":["menu","bar-widget"] ...}
DIAG shell.pluginId=<id>
DIAG shell.appLibrary direto=null
```
So `manifestHasKind` is satisfied and `pluginAppLibraryFor()` still yields nothing usable by the time the plugin reads it. `PluginAppLibraryApi` instances are created with `createObject(null, {...})` and only referenced from JS-side caches — worth checking whether they survive collection.
The app data itself is fine: `DesktopEntries.applications.values` holds 73 entries, and `AppSearch.sortedEntries()` returns all 73. Only the delivery to the plugin is broken.
### Reproduce
```bash
mkdir -p ~/.config/omarchy/plugins/repro.menutest
cp /usr/share/omarchy/shell/plugins/menu/{Menu.qml,MenuModel.js} \
~/.config/omarchy/plugins/repro.menutest/
cat > ~/.config/omarchy/plugins/repro.menutest/manifest.json <<'JSON'
{
"schemaVersion": 1,
"id": "repro.menutest",
"name": "Repro menu",
"version": "1.0.0",
"author": "repro",
"description": "Unmodified copy of omarchy.menu",
"kinds": ["menu"],
"keepLoaded": true,
"entryPoints": { "menu": "Menu.qml" }
}
JSON
```
Add `{"id": "repro.menutest"}` to `plugins` in `~/.config/omarchy/shell.json`, restart the shell, then:
```bash
omarchy-shell shell toggle repro.menutest '{"menu":"apps"}'
```
**Expected:** the installed applications, as `omarchy.menu` shows them.
**Actual:** "Nothing here yet".
The files are byte-identical to the shipped plugin — only the manifest id differs. Adding one `console.log` to `mergeAppRows()` in that copy prints:
```
REPRO appLibrary=null shell=PluginShellApi_QMLTYPE_26(0x...)
```
---
## Bug 2 — `__sourceDir` is stripped, so third-party plugins cannot find their own bundled files
`publicPluginManifest()` deletes `__sourceDir` for anything not first-party (`shell.qml:318-322`):
```qml
function publicPluginManifest(manifest) {
if (!manifest) return null
if (manifest.__isFirstParty) return manifest
var copy = JSON.parse(JSON.stringify(manifest))
delete copy.__sourceDir
...
```
Any third-party plugin shipping a helper script or asset alongside its QML uses `manifest.__sourceDir` to locate it. With the field gone the path resolves empty and the plugin runs `bash ""`, which fails silently.
Concretely, the third-party Workload plugin resolves its collector as:
```qml
readonly property string sourceDir: manifest && manifest.__sourceDir
? String(manifest.__sourceDir).replace(/^file:\/\//, "").replace(/\/$/, "") : ""
readonly property string collector: sourceDir ? sourceDir + "/workload-collector" : ""
```
On 4.0.3 every reading renders as zero — 0 °C, 0 MHz, 0 threads, 0.0/0.0 GB memory, 0/0 GB disk — while running the same script by hand returns a full JSON snapshot immediately. No error is logged.
`sourceDir` is still put into bar-widget registry metadata (`shell.qml:1410`), but `PluginBarWidgetRegistryApi` does not expose it, so there is no supported way for a third-party plugin to find its own directory. `Qt.resolvedUrl(".")` works as a local workaround, but plugins that follow the documented `__sourceDir` contract all break on upgrade.
---
## Suggested direction
If restricting these is deliberate, the replacements need to be reachable and the removals announced — a third-party plugin currently has no supported way to (a) list applications or (b) locate its own bundled files, and both fail without a diagnostic. If it is not deliberate, `appLibrary` delivery and `__sourceDir` (or an exposed equivalent on the registry API) should be restored for third-party plugins.
A warning when a plugin reads a stripped/nulled member would have turned hours of bisecting into one log line.
## System
- Omarchy 4.0.3-1 (regressed from 4.0.1-1)
- omarchy-settings 4.0.3-1
- quickshell 0.3.1-1
- qt6-base 6.11.2-3
- Arch Linux, Hyprland, x86_64
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.