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.
bar.shell.serviceFor() always returns null for installed third-party plugins
Reported by n30nf4nt0m · filed Sep 15, 2026Closed upstream on 2026-09-21 by bjarneo via an automated message, as a duplicate of #11796 (R-577). 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
What happened
Third-party (installed) bar-widget plugins that also ship a service kind can never
look up their own service through bar.shell.serviceFor(id). The call always
returns null, even when the plugin is asking for its own id and the service is
running. As a result, any panel UI wired up the "normal" way — checking if (svc) ...
before calling into it — silently no-ops on every interaction. The panel still opens
and looks fully interactive (buttons highlight on hover, etc.), so the plugin appears
"broken"/unresponsive with no error anywhere, which is a nasty one to debug from the
plugin author's side.
Root cause
In shell/shell.qml, pluginShellForBarEntry() builds the PluginShellApi facade
handed to installed third-party bar widgets via Bar.qml's pluginBarApiFor() (used
whenever ModuleSlot.registered is false, i.e. any plugin loaded through the file-basedqmlLoader path rather than the compiled registry):
function pluginShellForBarEntry(ownerId, moduleName) {
...
var api = pluginShellApiComponent.createObject(null, {
pluginId: target,
barConfig: shell.publicBarConfig(),
_summon: function(requestedId, payloadJson) { ... },
_hide: function(requestedId) { ... },
_toggle: function(requestedId, payloadJson) { ... },
_isOpen: function(requestedId) { ... },
_updateSettings: function(requestedId, settings) { ... }
})
...
}_serviceLookup is never assigned here. PluginShellApi.serviceFor(id) is:
function serviceFor(id) {
return _serviceLookup ? _serviceLookup(String(id || "")) : null
}Since _serviceLookup stays at its default null, serviceFor() unconditionally
returns null for every id — including the plugin's own — for any widget going
through the bar-entry facade. Compare with pluginShellForId() /createScopedPluginShell() (the path used for registered/first-party widgets), which
does wire up service lookup.
This affects the officially-documented plugin pattern of shipping both a service and
a bar-widget kind in one plugin and having the bar widget readbar.shell.serviceFor(pluginId) to get a live reference to its own service (this is
exactly the pattern several community plugins use, e.g. any plugin with aPresets.js/Service.qml/BarWidget.qml/Panel.qml shape).
Steps to reproduce
- Install any third-party plugin with
"kinds": ["service", "bar-widget"]whose
BarWidget.qml does:
readonly property var svc: bar && bar.shell ? bar.shell.serviceFor("your.plugin.id") : null- Open the plugin's panel from the bar icon.
- Click any control that's gated behind
if (svc) svc.doThing()— nothing happens. svcisnullthe entire time; UI elements that displaysvc-derived state (e.g.
svc.someProperty) silently show whatever hardcoded fallback the plugin coded for
the null case, not the plugin's real state.
Expected behavior
bar.shell.serviceFor(id) should resolve a plugin's own service instance (at minimum
for id === pluginId) the same way it does for first-party/registered widgets, so the
documented service+bar-widget plugin pattern actually works for installed third-party
plugins.
Workaround
A plugin can sidestep this by having its BarWidget.qml load its own Service.qml
instance directly via a Loader instead of going through bar.shell.serviceFor().
Since the service's effects (state file, hyprctl calls, etc.) are idempotent and
re-read from shared persisted state on load, this keeps things in sync in practice —
but it shouldn't be necessary.
System details
- Omarchy version: 4.0.3-1
- Hyprland: 0.56.2
- GPU: Intel Iris Xe Graphics (TigerLake-LP GT2)
- CPU: 11th Gen Intel Core i7-1195G7
View raw text
## What happened
Third-party (installed) bar-widget plugins that also ship a `service` kind can never
look up their own service through `bar.shell.serviceFor(id)`. The call always
returns `null`, even when the plugin is asking for its own id and the service is
running. As a result, any panel UI wired up the "normal" way — checking `if (svc) ...`
before calling into it — silently no-ops on every interaction. The panel still opens
and looks fully interactive (buttons highlight on hover, etc.), so the plugin appears
"broken"/unresponsive with no error anywhere, which is a nasty one to debug from the
plugin author's side.
## Root cause
In `shell/shell.qml`, `pluginShellForBarEntry()` builds the `PluginShellApi` facade
handed to installed third-party bar widgets via `Bar.qml`'s `pluginBarApiFor()` (used
whenever `ModuleSlot.registered` is false, i.e. any plugin loaded through the file-based
`qmlLoader` path rather than the compiled registry):
```qml
function pluginShellForBarEntry(ownerId, moduleName) {
...
var api = pluginShellApiComponent.createObject(null, {
pluginId: target,
barConfig: shell.publicBarConfig(),
_summon: function(requestedId, payloadJson) { ... },
_hide: function(requestedId) { ... },
_toggle: function(requestedId, payloadJson) { ... },
_isOpen: function(requestedId) { ... },
_updateSettings: function(requestedId, settings) { ... }
})
...
}
```
`_serviceLookup` is never assigned here. `PluginShellApi.serviceFor(id)` is:
```qml
function serviceFor(id) {
return _serviceLookup ? _serviceLookup(String(id || "")) : null
}
```
Since `_serviceLookup` stays at its default `null`, `serviceFor()` unconditionally
returns `null` for every id — including the plugin's own — for any widget going
through the bar-entry facade. Compare with `pluginShellForId()` /
`createScopedPluginShell()` (the path used for registered/first-party widgets), which
does wire up service lookup.
This affects the officially-documented plugin pattern of shipping both a `service` and
a `bar-widget` kind in one plugin and having the bar widget read
`bar.shell.serviceFor(pluginId)` to get a live reference to its own service (this is
exactly the pattern several community plugins use, e.g. any plugin with a
`Presets.js`/`Service.qml`/`BarWidget.qml`/`Panel.qml` shape).
## Steps to reproduce
1. Install any third-party plugin with `"kinds": ["service", "bar-widget"]` whose
`BarWidget.qml` does:
```qml
readonly property var svc: bar && bar.shell ? bar.shell.serviceFor("your.plugin.id") : null
```
2. Open the plugin's panel from the bar icon.
3. Click any control that's gated behind `if (svc) svc.doThing()` — nothing happens.
4. `svc` is `null` the entire time; UI elements that display `svc`-derived state (e.g.
`svc.someProperty`) silently show whatever hardcoded fallback the plugin coded for
the null case, not the plugin's real state.
## Expected behavior
`bar.shell.serviceFor(id)` should resolve a plugin's own service instance (at minimum
for `id === pluginId`) the same way it does for first-party/registered widgets, so the
documented service+bar-widget plugin pattern actually works for installed third-party
plugins.
## Workaround
A plugin can sidestep this by having its `BarWidget.qml` load its own `Service.qml`
instance directly via a `Loader` instead of going through `bar.shell.serviceFor()`.
Since the service's effects (state file, `hyprctl` calls, etc.) are idempotent and
re-read from shared persisted state on load, this keeps things in sync in practice —
but it shouldn't be necessary.
## System details
- Omarchy version: 4.0.3-1
- Hyprland: 0.56.2
- GPU: Intel Iris Xe Graphics (TigerLake-LP GT2)
- CPU: 11th Gen Intel Core i7-1195G7
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.