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 shell plugins silently cannot read shell.json config (omarchy.lock clone loses all idle settings)
Reported by TripleU613 · filed Sep 8, 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
Summary
A cloned shell plugin cannot read shell.json config, and the failure is completely silent —
every setting reads as its hardcoded default with no error, no warning, and no log line. A clone
of omarchy.lock loses all of its idle settings this way, so features the user has explicitly
enabled in shell.json never run and there is nothing to indicate why.
I lost a while to this: my cloned lock plugin reported lockScreensaver as disabled for weeks
while shell.json plainly had "lockScreensaver": true. The lock screensaver had never run once.
Cause
Two gates, both in shell/shell.qml:
1. `pluginShellFor(manifest)` (~line 725):
function pluginShellFor(manifest) {
if (!manifest || manifest.__isFirstParty) return shell
...
}Only first-party plugins get the real shell. Everything else gets a PluginShellApi proxy,
and that proxy has no `shellConfig` property at all (shell/services/PluginShellApi.qml
exposes appLibrary, bar, barConfig, idleConfig, and private _-prefixed members).
So shell.shellConfig.idle — which is exactly what the first-party plugins/lock/Service.qml
uses — is undefined in any clone of it.
2. `publicIdleConfigFor(manifest)` (~line 358):
function publicIdleConfigFor(manifest) {
var metadata = manifest && Util.isPlainObject(manifest.omarchy) ? manifest.omarchy : null
if (!metadata || String(metadata.clonedFrom || "") !== "omarchy.idle") return ({})
...
}The proxy does expose idleConfig, but only for clones of omarchy.idle. A clone ofomarchy.lock has clonedFrom: "omarchy.lock", so this returns ({}) too.
Net result: a clone of omarchy.lock reads empty via both available paths.
Reproduction
- Clone
omarchy.lock(manifest gets"omarchy": { "clonedFrom": "omarchy.lock" }). - Enable the clone and disable the original in
shell.json. - Set an
idlevalue the clone depends on, e.g.:
"idle": { "screensaver": 150, "lock": 86400, "lockScreensaver": true,
"lockScreensaverDelay": 20, "lockScreensaverDuration": 0 }- The clone's
idleConfigis({}). Everything derived from it falls back to defaults —
lockScreensaver reads as not-true, so the feature stays off.
Observed via the clone's own IPC: ssEnabled: false while shell.json said true.
Why it is easy to misdiagnose
The fallback defaults can coincide with the configured values, which hides the problem. In my
case lockScreensaverDelay: 20 and lockScreensaverDuration: 0 were both the configured
values and the hardcoded defaults, so the status output looked almost correct — only the
boolean differed, and nothing anywhere said "config not readable."
Suggested fix
Any of these would have prevented it, roughly in order of preference:
- Let
publicIdleConfigForserve theidleblock to clones that legitimately need it —
omarchy.lock reads shellConfig.idle as a first-party, so its clones need the same data.
Perhaps derive the allowance from what the source plugin itself reads, rather than
hardcoding a single clonedFrom value.
2. Expose a read-only, sanitised shellConfig on PluginShellApi generally.
3. At minimum, warn. A clone reading shell.shellConfig and getting undefined currently
gets silence; a console.warn naming the plugin and the unreachable key would turn this
from an invisible failure into a one-line diagnosis.
Workaround for plugin authors
Read the config file directly, falling back through both API paths first:
property var fileIdleConfig: ({})
readonly property var idleConfig: {
if (shell && shell.shellConfig && shell.shellConfig.idle) return shell.shellConfig.idle
if (shell && shell.idleConfig && Object.keys(shell.idleConfig).length > 0) return shell.idleConfig
return fileIdleConfig
}
FileView {
path: root.home + "/.config/omarchy/shell.json"
watchChanges: true
printErrors: false
onLoaded: {
try {
var parsed = JSON.parse(text())
root.fileIdleConfig = parsed && parsed.idle ? parsed.idle : ({})
} catch (error) {
root.fileIdleConfig = ({})
}
}
onLoadFailed: root.fileIdleConfig = ({})
onFileChanged: reload()
}Confirmed working — the setting reads correctly after omarchy-restart-shell.
Environment
| omarchy | omarchy-dev 4.0.0.r2071.ga703092-1 |
| quickshell | 0.3.1-1 |
| Hyprland | 0.56.2-2 |
| Kernel | 7.2.3-arch1-3 (Arch) |
Related
Separately, Omarchy's lock blanks the display via DPMS ~5s after locking
(idleBlankTimer { interval: 5000 } → omarchy-brightness-display off). On hardware wheredpms enable does not retrain the link, that leaves the user unable to restore their display
by any input — filed upstream as hyprwm/Hyprland#16183. Worth noting because a defensive
modeset in omarchy-system-wake (rather than trusting dpmsStatus) would make Omarchy
resilient to it.
View raw text
## Summary
A cloned shell plugin cannot read `shell.json` config, and the failure is **completely silent** —
every setting reads as its hardcoded default with no error, no warning, and no log line. A clone
of `omarchy.lock` loses all of its `idle` settings this way, so features the user has explicitly
enabled in `shell.json` never run and there is nothing to indicate why.
I lost a while to this: my cloned lock plugin reported `lockScreensaver` as disabled for weeks
while `shell.json` plainly had `"lockScreensaver": true`. The lock screensaver had never run once.
## Cause
Two gates, both in `shell/shell.qml`:
**1. `pluginShellFor(manifest)`** (~line 725):
```qml
function pluginShellFor(manifest) {
if (!manifest || manifest.__isFirstParty) return shell
...
}
```
Only first-party plugins get the real `shell`. Everything else gets a `PluginShellApi` proxy,
and that proxy has **no `shellConfig` property at all** (`shell/services/PluginShellApi.qml`
exposes `appLibrary`, `bar`, `barConfig`, `idleConfig`, and private `_`-prefixed members).
So `shell.shellConfig.idle` — which is exactly what the first-party `plugins/lock/Service.qml`
uses — is `undefined` in any clone of it.
**2. `publicIdleConfigFor(manifest)`** (~line 358):
```qml
function publicIdleConfigFor(manifest) {
var metadata = manifest && Util.isPlainObject(manifest.omarchy) ? manifest.omarchy : null
if (!metadata || String(metadata.clonedFrom || "") !== "omarchy.idle") return ({})
...
}
```
The proxy *does* expose `idleConfig`, but only for clones of `omarchy.idle`. A clone of
`omarchy.lock` has `clonedFrom: "omarchy.lock"`, so this returns `({})` too.
Net result: a clone of `omarchy.lock` reads empty via **both** available paths.
## Reproduction
1. Clone `omarchy.lock` (manifest gets `"omarchy": { "clonedFrom": "omarchy.lock" }`).
2. Enable the clone and disable the original in `shell.json`.
3. Set an `idle` value the clone depends on, e.g.:
```json
"idle": { "screensaver": 150, "lock": 86400, "lockScreensaver": true,
"lockScreensaverDelay": 20, "lockScreensaverDuration": 0 }
```
4. The clone's `idleConfig` is `({})`. Everything derived from it falls back to defaults —
`lockScreensaver` reads as not-`true`, so the feature stays off.
Observed via the clone's own IPC: `ssEnabled: false` while `shell.json` said `true`.
## Why it is easy to misdiagnose
The fallback defaults can coincide with the configured values, which hides the problem. In my
case `lockScreensaverDelay: 20` and `lockScreensaverDuration: 0` were **both** the configured
values *and* the hardcoded defaults, so the status output looked almost correct — only the
boolean differed, and nothing anywhere said "config not readable."
## Suggested fix
Any of these would have prevented it, roughly in order of preference:
1. Let `publicIdleConfigFor` serve the `idle` block to clones that legitimately need it —
`omarchy.lock` reads `shellConfig.idle` as a first-party, so its clones need the same data.
Perhaps derive the allowance from what the source plugin itself reads, rather than
hardcoding a single `clonedFrom` value.
2. Expose a read-only, sanitised `shellConfig` on `PluginShellApi` generally.
3. At minimum, **warn**. A clone reading `shell.shellConfig` and getting `undefined` currently
gets silence; a `console.warn` naming the plugin and the unreachable key would turn this
from an invisible failure into a one-line diagnosis.
## Workaround for plugin authors
Read the config file directly, falling back through both API paths first:
```qml
property var fileIdleConfig: ({})
readonly property var idleConfig: {
if (shell && shell.shellConfig && shell.shellConfig.idle) return shell.shellConfig.idle
if (shell && shell.idleConfig && Object.keys(shell.idleConfig).length > 0) return shell.idleConfig
return fileIdleConfig
}
FileView {
path: root.home + "/.config/omarchy/shell.json"
watchChanges: true
printErrors: false
onLoaded: {
try {
var parsed = JSON.parse(text())
root.fileIdleConfig = parsed && parsed.idle ? parsed.idle : ({})
} catch (error) {
root.fileIdleConfig = ({})
}
}
onLoadFailed: root.fileIdleConfig = ({})
onFileChanged: reload()
}
```
Confirmed working — the setting reads correctly after `omarchy-restart-shell`.
## Environment
| | |
|---|---|
| omarchy | omarchy-dev 4.0.0.r2071.ga703092-1 |
| quickshell | 0.3.1-1 |
| Hyprland | 0.56.2-2 |
| Kernel | 7.2.3-arch1-3 (Arch) |
## Related
Separately, Omarchy's lock blanks the display via DPMS ~5s after locking
(`idleBlankTimer { interval: 5000 }` → `omarchy-brightness-display off`). On hardware where
`dpms enable` does not retrain the link, that leaves the user unable to restore their display
by any input — filed upstream as hyprwm/Hyprland#16183. Worth noting because a defensive
modeset in `omarchy-system-wake` (rather than trusting `dpmsStatus`) would make Omarchy
resilient to it.
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 02:13 UTC