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.
Plugin services receive shell.json changes one step late: syncPluginApis() reads the stale barConfig binding
Reported by elephantum · filed Sep 12, 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
System details
Intel Core Ultra 5 125U, Intel Meteor Lake-P graphics, Omarchy 4.0.3-1 (quickshell 0.3.1-1, kernel 7.2.3-arch1-3). Hardware-independent: this is QML evaluation order in shell/shell.qml, and it's unchanged on quattro.
What's wrong?
Third-party plugin services receive shell.json changes one change late. The barConfig on their scoped PluginShellApi is refreshed with the config from before the latest write.
Cause. onShellConfigChanged calls pluginRegistry.pluginsChanged(), which runs syncPluginApis(), which does shellApi.barConfig = shell.publicBarConfig(). publicBarConfig() reads the readonly property var barConfig: shellConfig && … binding. Inside a property's own change handler, bindings that depend on that property have not re-evaluated yet, so publicBarConfig() returns the previous bar config. The same applies to _pluginBarEntryShellApis[…].barConfig. publicIdleConfigFor() is not affected, because it reads shell.shellConfig.idle directly.
Minimal repro (qs -p shell.qml):
import QtQuick
import Quickshell
ShellRoot {
id: s
property var shellConfig: ({ bar: { v: 0 } })
readonly property var barConfig: shellConfig.bar
onShellConfigChanged: console.warn("handler sees", s.barConfig.v, "actual", s.shellConfig.bar.v)
Component.onCompleted: { shellConfig = { bar: { v: 1 } }; shellConfig = { bar: { v: 2 } } }
}handler sees 0 actual 0
handler sees 0 actual 1
handler sees 1 actual 2Real-world impact. In digitalfrost84/omarchy-auto-dark-mode-plugin, the bar widget toggles automatic through updateEntryInline(). The write to shell.json is correct, as confirmed with inotifywait. The plugin's service, which reads its entry from shell.barConfig, still sees the previous value. The UI appears to ignore the click, so the user clicks again. Now the service runs with automatic: true while shell.json holds false, and automatic switching is off after the next reboot. Bar widgets themselves are fine, because they get settings through onBarConfigChanged / applySettingsDelta(). Only the service path lags. Plugin issue: digitalfrost84/omarchy-auto-dark-mode-plugin#2
Steps to reproduce
1. Install a plugin with a service that reads its bar entry from shell.barConfig, e.g. the plugin above.
2. Change one of its inline settings through its widget (or updateEntryInline).
3. The service's shell.barConfig still holds the old value. It only catches up on the next config change or plugin rescan.
Suggested fix. Have publicBarConfig() derive from shellConfig directly, like publicIdleConfigFor() does:
function publicBarConfig() {
var config = shell.shellConfig && Util.isPlainObject(shell.shellConfig.bar)
? shell.shellConfig.bar : builtinShellConfig.bar
return JSON.parse(JSON.stringify(config))
}Alternatively, refresh the plugin APIs from onBarConfigChanged (or Qt.callLater) instead of from inside onShellConfigChanged.
Related but distinct: #7691 (slot entry goes stale after re-creation), #11079 (facade lifecycle; it keeps the same ordering).
View raw text
### System details
Intel Core Ultra 5 125U, Intel Meteor Lake-P graphics, Omarchy 4.0.3-1 (quickshell 0.3.1-1, kernel 7.2.3-arch1-3). Hardware-independent: this is QML evaluation order in `shell/shell.qml`, and it's unchanged on `quattro`.
### What's wrong?
Third-party plugin services receive `shell.json` changes **one change late**. The `barConfig` on their scoped `PluginShellApi` is refreshed with the config from *before* the latest write.
**Cause.** `onShellConfigChanged` calls `pluginRegistry.pluginsChanged()`, which runs `syncPluginApis()`, which does `shellApi.barConfig = shell.publicBarConfig()`. `publicBarConfig()` reads the `readonly property var barConfig: shellConfig && …` binding. Inside a property's own change handler, bindings that depend on that property have not re-evaluated yet, so `publicBarConfig()` returns the previous bar config. The same applies to `_pluginBarEntryShellApis[…].barConfig`. `publicIdleConfigFor()` is not affected, because it reads `shell.shellConfig.idle` directly.
Minimal repro (`qs -p shell.qml`):
```qml
import QtQuick
import Quickshell
ShellRoot {
id: s
property var shellConfig: ({ bar: { v: 0 } })
readonly property var barConfig: shellConfig.bar
onShellConfigChanged: console.warn("handler sees", s.barConfig.v, "actual", s.shellConfig.bar.v)
Component.onCompleted: { shellConfig = { bar: { v: 1 } }; shellConfig = { bar: { v: 2 } } }
}
```
```
handler sees 0 actual 0
handler sees 0 actual 1
handler sees 1 actual 2
```
**Real-world impact.** In [digitalfrost84/omarchy-auto-dark-mode-plugin](https://github.com/digitalfrost84/omarchy-auto-dark-mode-plugin), the bar widget toggles `automatic` through `updateEntryInline()`. The write to `shell.json` is correct, as confirmed with `inotifywait`. The plugin's service, which reads its entry from `shell.barConfig`, still sees the previous value. The UI appears to ignore the click, so the user clicks again. Now the service runs with `automatic: true` while `shell.json` holds `false`, and automatic switching is off after the next reboot. Bar widgets themselves are fine, because they get settings through `onBarConfigChanged` / `applySettingsDelta()`. Only the service path lags. Plugin issue: digitalfrost84/omarchy-auto-dark-mode-plugin#2
**Steps to reproduce**
1. Install a plugin with a service that reads its bar entry from `shell.barConfig`, e.g. the plugin above.
2. Change one of its inline settings through its widget (or `updateEntryInline`).
3. The service's `shell.barConfig` still holds the old value. It only catches up on the next config change or plugin rescan.
**Suggested fix.** Have `publicBarConfig()` derive from `shellConfig` directly, like `publicIdleConfigFor()` does:
```qml
function publicBarConfig() {
var config = shell.shellConfig && Util.isPlainObject(shell.shellConfig.bar)
? shell.shellConfig.bar : builtinShellConfig.bar
return JSON.parse(JSON.stringify(config))
}
```
Alternatively, refresh the plugin APIs from `onBarConfigChanged` (or `Qt.callLater`) instead of from inside `onShellConfigChanged`.
Related but distinct: #7691 (slot entry goes stale after re-creation), #11079 (facade lifecycle; it keeps the same ordering).
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 12:16 UTC