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-1694GitHub issuePublicDecided· Out of scope

Cloning a service + bar-widget plugin leaves the widget dead: firstPartyServiceFor() has no clonedFrom routing

Reported by y4nder · filed Aug 18, 2026 · open at source

Decided: Out of scope — decided by agents · verified 3 days ago.

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?
AreaOutside the audited area· 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

Cloning a service + bar-widget plugin leaves the widget dead: firstPartyServiceFor() has no clonedFrom routingrevision 1 · captured 2026-09-28 21:29 UTC via poll

What happened

omarchy plugin clone omarchy.media succeeds, enables the clone, and places it in the bar — but the cloned widget renders as a zero-width, invisible slot forever. There is no error, and the bar simply has a gap where the media widget was.

$ omarchy plugin clone omarchy.media
Cloned omarchy.media to /home/yander/.config/omarchy/plugins/yander.media and switched to yander.media

$ omarchy-shell shell debugBarGeometry | jq '.[] | select(.id | test("media"))'
{"id":"yander.media","section":"center","x":1016,"y":0,"width":0,"height":0,
 "visible":false,"itemVisible":false,"itemWidth":0,"itemHeight":26}

This is not specific to the marquee or to media playback state — MPRIS was actively playing throughout.

Expected behavior

A cloned plugin should behave exactly like the built-in it replaced. omarchy plugin clone is the documented way to customize a built-in widget, and the clone script's own comment states the intent:

Keep built-in ids inside the plugin code as stable IPC targets. The shell uses clonedFrom to route those calls to this local manifest id.

Root cause

That routing exists — but it was never applied to service lookups.

omarchy.media is both a service and a bar-widget. Its BarWidget.qml reaches its own service by the built-in id:

// shell/plugins/services/media/BarWidget.qml:10
readonly property var mediaService: bar?.shell?.firstPartyServiceFor("omarchy.media")

Cloning writes omarchy.media into disabledPlugins, and _syncServices() only instantiates services for enabled plugins, destroying the rest:

// shell/shell.qml:333
if (!pluginRegistry.isEnabled(id)) continue
if (_services[id]) continue
ensureService(id)

So _services["omarchy.media"] is gone and the clone's service lives at _services["yander.media"]. But firstPartyServiceFor() is a plain dictionary lookup with no clonedFrom routing:

// shell/shell.qml:276-282
function serviceFor(pluginId) {
  return _services[String(pluginId)] || null
}

function firstPartyServiceFor(pluginId) {
  return serviceFor(pluginId)
}

Every other cross-plugin entry point does route through resolveEnabledId() first — shell.qml:442, 482, 499, 512, 569. Service lookup is the one path that was missed.

Result: mediaService is null → activePlayer is null → hasMedia is false → visible: hasMedia and implicitWidth: hasMedia ? … : 0 collapse the widget to nothing.

Confirmed at runtime with a temporary probe inside the cloned widget:

MEDIADIAG bar=y shell=y registry=y
  resolveEnabledId=yander.media
  serviceFor(rid)=OBJ
  serviceFor(omarchy.media)=null      <- what the widget actually asks for
  serviceFor(yander.media)=OBJ        <- where the service actually is
  mediaService=OBJ activePlayer=OBJ hasMedia=true visible=true

(that probe already had the fix below applied, which is why the routed lookup resolves)

Scope

This affects any cloned plugin that is both a service and a bar-widget and looks up its own service by the built-in id — not just media. It is distinct from #7253 / #6971 / #6915, which are about bar-kind plugins failing to mount.

There is a second casualty: the built-in audio panel makes the same unrouted call, so its now-playing section goes blank while any media clone is active, even though the panel itself was never cloned:

// shell/plugins/panels/audio/Panel.qml:20
readonly property var mediaService: bar?.shell?.firstPartyServiceFor("omarchy.media")

Steps to reproduce

  1. Start any MPRIS source playing (Spotify, a browser tab) and confirm the media widget shows in the bar.
  2. omarchy plugin clone omarchy.media
  3. omarchy restart shell
  4. The media widget is gone from the bar. omarchy-shell shell debugBarGeometry shows the entry present with width: 0, visible: false.
  5. Open the audio panel — the now-playing section is blank too.

Suggested fix

Route the first-party lookup the same way the IPC paths already do, leaving the raw serviceFor() untouched:

 function firstPartyServiceFor(pluginId) {
-  return serviceFor(pluginId)
+  var id = pluginRegistry ? pluginRegistry.resolveEnabledId(pluginId) : pluginId
+  return serviceFor(id)
 }

resolveEnabledId() already does exactly this mapping for IPC and returns the id unchanged when no clone is enabled, so built-in behavior is unaffected.

I could not patch shell.qml itself to test this, since /usr/share/omarchy is package-owned on an installed system. I verified the equivalent routing widget-side instead, in the clone — which exercises the same resolveEnabledId() → serviceFor() path:

readonly property var mediaService: {
  var shellRef = bar?.shell
  if (!shellRef) return null
  var registry = shellRef.pluginRegistry
  var serviceId = registry ? registry.resolveEnabledId("omarchy.media") : "omarchy.media"
  return shellRef.serviceFor(serviceId)
}

With that in place the widget resolves its service, hasMedia becomes true, and it renders at full width (width: 207, visible: true). Doing it centrally in firstPartyServiceFor() would fix every cloned service consumer at once, including the audio panel above, which a widget-side workaround cannot reach.

One note for anyone reproducing: the fix needs omarchy restart shell to take effect. Plugin hot-reload does not rebuild an existing widget's property bindings, so editing a readonly property binding in place appears to do nothing (possibly related to #6981).

Environment: Omarchy 4.0.0-1, Hyprland 0.56.2, Quickshell 0.3.0, kernel 7.1.8-arch1-3, Intel i5-9400F / RTX 2060, single 1080p monitor.

View raw text
## What happened

`omarchy plugin clone omarchy.media` succeeds, enables the clone, and places it in the bar — but the cloned widget renders as a zero-width, invisible slot forever. There is no error, and the bar simply has a gap where the media widget was.

```
$ omarchy plugin clone omarchy.media
Cloned omarchy.media to /home/yander/.config/omarchy/plugins/yander.media and switched to yander.media

$ omarchy-shell shell debugBarGeometry | jq '.[] | select(.id | test("media"))'
{"id":"yander.media","section":"center","x":1016,"y":0,"width":0,"height":0,
 "visible":false,"itemVisible":false,"itemWidth":0,"itemHeight":26}
```

This is not specific to the marquee or to media playback state — MPRIS was actively playing throughout.

## Expected behavior

A cloned plugin should behave exactly like the built-in it replaced. `omarchy plugin clone` is the documented way to customize a built-in widget, and the clone script's own comment states the intent:

> Keep built-in ids inside the plugin code as stable IPC targets. The shell uses `clonedFrom` to route those calls to this local manifest id.

## Root cause

That routing exists — but it was never applied to service lookups.

`omarchy.media` is both a `service` and a `bar-widget`. Its `BarWidget.qml` reaches its own service by the built-in id:

```qml
// shell/plugins/services/media/BarWidget.qml:10
readonly property var mediaService: bar?.shell?.firstPartyServiceFor("omarchy.media")
```

Cloning writes `omarchy.media` into `disabledPlugins`, and `_syncServices()` only instantiates services for enabled plugins, destroying the rest:

```qml
// shell/shell.qml:333
if (!pluginRegistry.isEnabled(id)) continue
if (_services[id]) continue
ensureService(id)
```

So `_services["omarchy.media"]` is gone and the clone's service lives at `_services["yander.media"]`. But `firstPartyServiceFor()` is a plain dictionary lookup with no `clonedFrom` routing:

```qml
// shell/shell.qml:276-282
function serviceFor(pluginId) {
  return _services[String(pluginId)] || null
}

function firstPartyServiceFor(pluginId) {
  return serviceFor(pluginId)
}
```

Every other cross-plugin entry point *does* route through `resolveEnabledId()` first — `shell.qml:442`, `482`, `499`, `512`, `569`. Service lookup is the one path that was missed.

Result: `mediaService` is `null` → `activePlayer` is `null` → `hasMedia` is `false` → `visible: hasMedia` and `implicitWidth: hasMedia ? … : 0` collapse the widget to nothing.

Confirmed at runtime with a temporary probe inside the cloned widget:

```
MEDIADIAG bar=y shell=y registry=y
  resolveEnabledId=yander.media
  serviceFor(rid)=OBJ
  serviceFor(omarchy.media)=null      <- what the widget actually asks for
  serviceFor(yander.media)=OBJ        <- where the service actually is
  mediaService=OBJ activePlayer=OBJ hasMedia=true visible=true
```

(that probe already had the fix below applied, which is why the routed lookup resolves)

## Scope

This affects **any cloned plugin that is both a service and a bar-widget and looks up its own service by the built-in id** — not just media. It is distinct from #7253 / #6971 / #6915, which are about `bar`-kind plugins failing to mount.

There is a second casualty: the built-in audio panel makes the same unrouted call, so its now-playing section goes blank while any media clone is active, even though the panel itself was never cloned:

```qml
// shell/plugins/panels/audio/Panel.qml:20
readonly property var mediaService: bar?.shell?.firstPartyServiceFor("omarchy.media")
```

## Steps to reproduce

1. Start any MPRIS source playing (Spotify, a browser tab) and confirm the media widget shows in the bar.
2. `omarchy plugin clone omarchy.media`
3. `omarchy restart shell`
4. The media widget is gone from the bar. `omarchy-shell shell debugBarGeometry` shows the entry present with `width: 0, visible: false`.
5. Open the audio panel — the now-playing section is blank too.

## Suggested fix

Route the first-party lookup the same way the IPC paths already do, leaving the raw `serviceFor()` untouched:

```diff
 function firstPartyServiceFor(pluginId) {
-  return serviceFor(pluginId)
+  var id = pluginRegistry ? pluginRegistry.resolveEnabledId(pluginId) : pluginId
+  return serviceFor(id)
 }
```

`resolveEnabledId()` already does exactly this mapping for IPC and returns the id unchanged when no clone is enabled, so built-in behavior is unaffected.

I could not patch `shell.qml` itself to test this, since `/usr/share/omarchy` is package-owned on an installed system. I verified the equivalent routing widget-side instead, in the clone — which exercises the same `resolveEnabledId()` → `serviceFor()` path:

```qml
readonly property var mediaService: {
  var shellRef = bar?.shell
  if (!shellRef) return null
  var registry = shellRef.pluginRegistry
  var serviceId = registry ? registry.resolveEnabledId("omarchy.media") : "omarchy.media"
  return shellRef.serviceFor(serviceId)
}
```

With that in place the widget resolves its service, `hasMedia` becomes true, and it renders at full width (`width: 207, visible: true`). Doing it centrally in `firstPartyServiceFor()` would fix every cloned service consumer at once, including the audio panel above, which a widget-side workaround cannot reach.

One note for anyone reproducing: the fix needs `omarchy restart shell` to take effect. Plugin hot-reload does not rebuild an existing widget's property bindings, so editing a `readonly property` binding in place appears to do nothing (possibly related to #6981).

**Environment:** Omarchy 4.0.0-1, Hyprland 0.56.2, Quickshell 0.3.0, kernel 7.1.8-arch1-3, Intel i5-9400F / RTX 2060, single 1080p monitor.

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.

  • Out of scopeDecided by agents · verified · 2026-10-09 04:40 UTC