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

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.

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

bar.shell.serviceFor() always returns null for installed third-party pluginsrevision 1 · captured 2026-10-04 12:10 UTC via poll

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):

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 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:

   readonly property var svc: bar && bar.shell ? bar.shell.serviceFor("your.plugin.id") : null
  1. Open the plugin's panel from the bar icon.
  2. Click any control that's gated behind if (svc) svc.doThing() — nothing happens.
  3. 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
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.