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.
Cloned service plugins are invisible to firstPartyServiceFor() (breaks Stay Awake indicator after cloning omarchy.idle)
Reported by SisuAika · filed Sep 5, 2026Closed upstream on 2026-09-20 by bjarneo via an automated message, as a duplicate of #7355 (R-1694). 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 happens
After cloning a service-kind shell plugin with omarchy plugin clone, every other
component that looks the service up by its built-in id viashell.firstPartyServiceFor(...) gets null, and silently loses functionality.
Concrete case: omarchy plugin clone omarchy.idle (to customise the screensaver /
wake commands). Everything the clone itself does keeps working, but the Stay Awake
bar indicator goes dead — the icon still renders, but active is stuck false and
clicking it does nothing.
Steps to reproduce
omarchy plugin clone omarchy.idleomarchy restart shell- Hover the center of the bar and click the Stay Awake (☕ /
) indicator.
Expected: idle lock + screensaver get inhibited, indicator lights up.
Actual: nothing happens; stayAwake never changes.
The idle service itself is fine — driving it directly works:omarchy-shell idle disable → stayAwake:true, omarchy-shell idle enable → stayAwake:false.
Root cause
firstPartyServiceFor() / serviceFor() are not clone-aware, unlike the rest of
the shell.
shell/shell.qml:
function serviceFor(pluginId) {
return _services[String(pluginId)] || null
}
function firstPartyServiceFor(pluginId) {
return serviceFor(pluginId)
}_services is keyed by concrete manifest id (_syncServices() → ensureService(id)
iterating installedPlugins). After cloning, the running service is registered asgarrett.idle, so _services["omarchy.idle"] is undefined and the built-in is indisabledPlugins, so it never loads either.
Everywhere else, calls that name a built-in id are routed throughPluginRegistry.resolveEnabledId(), which maps omarchy.idle → the enabledclonedFrom: "omarchy.idle" manifest — shell.qml does this for summon(), panel
resolution, bar-widget lookup, etc. (5 call sites). serviceFor() is the one that
skips it.
This is contrary to the documented contract in bin/omarchy-plugin-clone:
# 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.Affected consumers in-tree
shell/plugins/bar/indicators/StayAwake.qml:7—firstPartyServiceFor("omarchy.idle")shell/plugins/bar/indicators/NightLight.qml:7—firstPartyServiceFor("omarchy.nightlight")shell/plugins/bar/indicators/Dnd.qml:8—firstPartyServiceFor("omarchy.notifications")shell/plugins/panels/audio/Panel.qml:20andshell/plugins/services/media/BarWidget.qml:10—firstPartyServiceFor("omarchy.media")
i.e. cloning omarchy.nightlight, omarchy.notifications or omarchy.media should
break their indicators/panels the same way.
Suggested fix
Route serviceFor() through resolveEnabledId() like the other lookups:
function serviceFor(pluginId) {
var id = shell.pluginRegistry
? shell.pluginRegistry.resolveEnabledId(pluginId)
: String(pluginId)
return _services[String(id)] || _services[String(pluginId)] || null
}Workaround
Also clone omarchy.indicators (it ships clonePaths for the indicator dir) and
point the cloned indicators/StayAwake.qml at the cloned service id, with a fallback:
readonly property var idleService: bar?.shell?.firstPartyServiceFor("garrett.idle")
|| bar?.shell?.firstPartyServiceFor("omarchy.idle")
|| nullSystem
- Omarchy 4.0.0-1
- Hyprland 0.56.2, Quickshell shell
- Kernel 7.1.8-arch1-3, AMD Ryzen 5 5600 / Radeon RX 5700 XT (amdgpu)
Full omarchy debug log available on request.
View raw text
## What happens
After cloning a **service**-kind shell plugin with `omarchy plugin clone`, every other
component that looks the service up by its built-in id via
`shell.firstPartyServiceFor(...)` gets `null`, and silently loses functionality.
Concrete case: `omarchy plugin clone omarchy.idle` (to customise the screensaver /
wake commands). Everything the clone itself does keeps working, but the **Stay Awake**
bar indicator goes dead — the icon still renders, but `active` is stuck `false` and
clicking it does nothing.
## Steps to reproduce
1. `omarchy plugin clone omarchy.idle`
2. `omarchy restart shell`
3. Hover the center of the bar and click the Stay Awake (☕ / ``) indicator.
Expected: idle lock + screensaver get inhibited, indicator lights up.
Actual: nothing happens; `stayAwake` never changes.
The idle service itself is fine — driving it directly works:
`omarchy-shell idle disable` → `stayAwake:true`, `omarchy-shell idle enable` → `stayAwake:false`.
## Root cause
`firstPartyServiceFor()` / `serviceFor()` are **not clone-aware**, unlike the rest of
the shell.
`shell/shell.qml`:
```qml
function serviceFor(pluginId) {
return _services[String(pluginId)] || null
}
function firstPartyServiceFor(pluginId) {
return serviceFor(pluginId)
}
```
`_services` is keyed by concrete manifest id (`_syncServices()` → `ensureService(id)`
iterating `installedPlugins`). After cloning, the running service is registered as
`garrett.idle`, so `_services["omarchy.idle"]` is `undefined` and the built-in is in
`disabledPlugins`, so it never loads either.
Everywhere else, calls that name a built-in id are routed through
`PluginRegistry.resolveEnabledId()`, which maps `omarchy.idle` → the enabled
`clonedFrom: "omarchy.idle"` manifest — `shell.qml` does this for `summon()`, panel
resolution, bar-widget lookup, etc. (5 call sites). `serviceFor()` is the one that
skips it.
This is contrary to the documented contract in `bin/omarchy-plugin-clone`:
```
# 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.
```
## Affected consumers in-tree
- `shell/plugins/bar/indicators/StayAwake.qml:7` — `firstPartyServiceFor("omarchy.idle")`
- `shell/plugins/bar/indicators/NightLight.qml:7` — `firstPartyServiceFor("omarchy.nightlight")`
- `shell/plugins/bar/indicators/Dnd.qml:8` — `firstPartyServiceFor("omarchy.notifications")`
- `shell/plugins/panels/audio/Panel.qml:20` and `shell/plugins/services/media/BarWidget.qml:10` — `firstPartyServiceFor("omarchy.media")`
i.e. cloning `omarchy.nightlight`, `omarchy.notifications` or `omarchy.media` should
break their indicators/panels the same way.
## Suggested fix
Route `serviceFor()` through `resolveEnabledId()` like the other lookups:
```qml
function serviceFor(pluginId) {
var id = shell.pluginRegistry
? shell.pluginRegistry.resolveEnabledId(pluginId)
: String(pluginId)
return _services[String(id)] || _services[String(pluginId)] || null
}
```
## Workaround
Also clone `omarchy.indicators` (it ships `clonePaths` for the indicator dir) and
point the cloned `indicators/StayAwake.qml` at the cloned service id, with a fallback:
```qml
readonly property var idleService: bar?.shell?.firstPartyServiceFor("garrett.idle")
|| bar?.shell?.firstPartyServiceFor("omarchy.idle")
|| null
```
## System
- Omarchy 4.0.0-1
- Hyprland 0.56.2, Quickshell shell
- Kernel 7.1.8-arch1-3, AMD Ryzen 5 5600 / Radeon RX 5700 XT (amdgpu)
Full `omarchy debug` log available on request.
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.