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.
Settings changed during startup overwrite shell.json with the shipped defaults
Reported by leon-biobuild · filed Sep 11, 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 happens
Any change to the shell configuration made before ~/.config/omarchy/shell.json
has finished loading persists the shipped defaults over the user's file. The
bar layout, every widget's settings and every third-party plugin are replaced,
with no backup written.
Why
shell.qml initialises the live config to the built-in defaults and replaces it
only once the user's file has loaded — asynchronously, through a FileView with
no blockLoading:
property var shellConfig: builtinShellConfig
FileView {
id: userConfigFile
path: shell.userConfigPath
watchChanges: true
atomicWrites: true
onLoaded: shell.applyShellConfig()
}Every settings change goes through mutateShellConfig(), which deep-copies
whatever shellConfig holds at that instant and persists the whole thing:
function mutateShellConfig(mutator) {
var copy = JSON.parse(JSON.stringify(shellConfig || builtinShellConfig))
mutator(copy)
persistShellConfig(copy) // writes the entire file
}So a mutation inside the startup window writes the defaults to disk. The|| builtinShellConfig fallback suggests the null case was considered; the
initialiser makes the property never null, so the guard cannot fire and the
defaults are copied silently instead.
Reproducing it
The startup window is short, so the reliable way in is a post-boot hook —autostart.lua runs sleep 2 && omarchy-hook post-boot, which lands inside it:
# ~/.config/omarchy/hooks/post-boot.d/repro
omarchy plugin enable some.plugin # any settings change will doReboot. ~/.config/omarchy/shell.json is now byte-for-byte/usr/share/omarchy/config/omarchy/shell.json plus whatever that one change
added — 1234 bytes against a 1194-byte default, in my case. It happens on every
boot for as long as the hook is there.
I hit this for real twice on 4.0.3 before finding the cause: once seemingly on
its own, and then on every boot after I added a hook that re-enabled a plugin.
Anything that writes a setting early is a candidate — the Google Calendar widget
persists lastCalendarPull, and Power Manager writes the lock timeout.
Why it is hard to diagnose
Nothing reports it. Configuration Loaded is logged as usual, no warning is
emitted, and no backup is left behind. For a plugin bound to a key, the symptom
is that the key silently stops working: the plugin is still on disk, the binding
is still in bindings.lua, hyprctl binds still lists it, and pressing it does
nothing, because what it toggles is no longer in the config.
Suggested fixes
Any one of these would close it:
- Read the user config with
blockLoading(andblockAllReads), so
shellConfig is never the defaults by the time anything can mutate it.
- Track loaded-ness explicitly and have mutateShellConfig() queue or refuse
until the file has loaded, rather than relying on a fallback that can't fire.
- Refuse to persist a config that is byte-identical to the shipped defaults when
the user's file on disk is not, and log it.
Happy to send a PR for whichever direction you prefer.
Version
Omarchy 4.0.3-1, Quickshell 0.3.1-1, Arch.
View raw text
### What happens
Any change to the shell configuration made before `~/.config/omarchy/shell.json`
has finished loading persists the **shipped defaults** over the user's file. The
bar layout, every widget's settings and every third-party plugin are replaced,
with no backup written.
### Why
`shell.qml` initialises the live config to the built-in defaults and replaces it
only once the user's file has loaded — asynchronously, through a `FileView` with
no `blockLoading`:
```qml
property var shellConfig: builtinShellConfig
FileView {
id: userConfigFile
path: shell.userConfigPath
watchChanges: true
atomicWrites: true
onLoaded: shell.applyShellConfig()
}
```
Every settings change goes through `mutateShellConfig()`, which deep-copies
whatever `shellConfig` holds at that instant and persists the whole thing:
```qml
function mutateShellConfig(mutator) {
var copy = JSON.parse(JSON.stringify(shellConfig || builtinShellConfig))
mutator(copy)
persistShellConfig(copy) // writes the entire file
}
```
So a mutation inside the startup window writes the defaults to disk. The
`|| builtinShellConfig` fallback suggests the null case was considered; the
initialiser makes the property never null, so the guard cannot fire and the
defaults are copied silently instead.
### Reproducing it
The startup window is short, so the reliable way in is a `post-boot` hook —
`autostart.lua` runs `sleep 2 && omarchy-hook post-boot`, which lands inside it:
```bash
# ~/.config/omarchy/hooks/post-boot.d/repro
omarchy plugin enable some.plugin # any settings change will do
```
Reboot. `~/.config/omarchy/shell.json` is now byte-for-byte
`/usr/share/omarchy/config/omarchy/shell.json` plus whatever that one change
added — 1234 bytes against a 1194-byte default, in my case. It happens on every
boot for as long as the hook is there.
I hit this for real twice on 4.0.3 before finding the cause: once seemingly on
its own, and then on every boot after I added a hook that re-enabled a plugin.
Anything that writes a setting early is a candidate — the Google Calendar widget
persists `lastCalendarPull`, and Power Manager writes the lock timeout.
### Why it is hard to diagnose
Nothing reports it. `Configuration Loaded` is logged as usual, no warning is
emitted, and no backup is left behind. For a plugin bound to a key, the symptom
is that the key silently stops working: the plugin is still on disk, the binding
is still in `bindings.lua`, `hyprctl binds` still lists it, and pressing it does
nothing, because what it toggles is no longer in the config.
### Suggested fixes
Any one of these would close it:
- Read the user config with `blockLoading` (and `blockAllReads`), so
`shellConfig` is never the defaults by the time anything can mutate it.
- Track loaded-ness explicitly and have `mutateShellConfig()` queue or refuse
until the file has loaded, rather than relying on a fallback that can't fire.
- Refuse to persist a config that is byte-identical to the shipped defaults when
the user's file on disk is not, and log it.
Happy to send a PR for whichever direction you prefer.
### Version
Omarchy 4.0.3-1, Quickshell 0.3.1-1, Arch.
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 11:52 UTC