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.
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
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
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
- Start any MPRIS source playing (Spotify, a browser tab) and confirm the media widget shows in the bar.
omarchy plugin clone omarchy.mediaomarchy restart shell- The media widget is gone from the bar.
omarchy-shell shell debugBarGeometryshows the entry present withwidth: 0, visible: false. - 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