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.
Truncated shell.json falls back to builtin defaults; the next config write persists them (bar resets, third-party plugins disabled)
Reported by alanwcurry-dev · filed Sep 23, 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
When ~/.config/omarchy/shell.json becomes empty (0 bytes) while the shell is running, the bar reverts to the stock layout and every third-party plugin is disabled — permanently, because the very next shell-side config write persists the shipped defaults over the user's file. No backup is written and nothing is logged.
This is the same mutateShellConfig fallback as #11349, but with a different trigger: #11349 is the startup window (a mutation landing before the async FileView load completes). This one is a runtime truncation — the user's file is already loaded and fine, then it becomes empty, and the shell turns that into a permanent reset.
Why
The truncation reaches the fallback through the FileView change/reload path:
// shell.qml
FileView {
id: userConfigFile
path: shell.userConfigPath
watchChanges: true
atomicWrites: true
onLoaded: shell.applyShellConfig()
onLoadFailed: function(error) { shell.applyShellConfig() }
onFileChanged: reload() // line 142
}When the file changes (including becoming 0 bytes), reload() → onLoaded → applyShellConfig():
// shell.qml:73-88
function applyShellConfig() {
var defaults = Util.isPlainObject(defaultsConfig) ? defaultsConfig : builtinShellConfig
var user = null
var userText = userConfigFile.text() || ""
if (userText.trim()) {
// parse; set user
}
shellConfig = user || defaults // empty file → user null → builtinShellConfig (stock)
}shellConfig is now the builtin stock config. The next plugin-initiated write — idle service, a widget persisting a setting, OmaSettings, omarchy bar set … — goes through mutateShellConfig():
// shell.qml:162-166 (also 1069 in updateEntryInline)
function mutateShellConfig(mutator) {
var copy = JSON.parse(JSON.stringify(shellConfig || builtinShellConfig))
mutator(copy)
persistShellConfig(copy) // writes the whole file — stock layout + plugins: []
}…which writes the stock config back to disk. A transient truncation is now a permanent reset, and because enablement is stored in shell.json, every third-party plugin drops off the bar at once.
Evidence
On an edge-channel install I've seen this ~14 times across a week. A path-unit guard I run snapshots and restores the file, so the timeline is recorded:
- 14 ×
shell.json.bak.EMPTY.*artifacts — the 0-byte truncations (Sep 16–19). - Guard log: after each truncation the file came back within ~2 s as a ~1370-byte config — the shipped default, not the user's (2312–2351-byte) config:
2026-09-21 21:04:50 was mid-write (1372 bytes) — snapshot updated
2026-09-22 20:37:01 was mid-write (1374 bytes) — snapshot updated- After restoring the user's config on Sep 21 22:55, the file was rewritten to stock again at 22:57 (
shell.json.bak.RESET.20260921225710, 1374 bytes) — a settings change landing after a truncation, persisting the defaults.
The spontaneous 0-byte truncation itself is a separate, still-unexplained bug (every occurrence lands within the minute of a shell config reload, often right after an omarchy update; the shell's own FileView.setText with atomicWrites: true is the only non-atomic writer involved). But even if that trigger is never fixed, the shell should not amplify it into a permanent reset.
Reproduce
On a machine with a non-default layout (a third-party plugin in the bar, anything in plugins[]):
- While the shell is running:
: > ~/.config/omarchy/shell.json - Trigger any shell-side config write, e.g.
omarchy bar set omarchy.clock format "dddd HH:mm"(or wait for the idle service / a widget to persist a setting). ~/.config/omarchy/shell.jsonis now byte-for-byte the shipped default plus that one change; the user's layout and every third-party plugin are gone, with no backup and no log line.
Suggested fixes
Any one of these would stop the amplifier (independent of, and complementary to, #11349):
- In
applyShellConfig, when the user file is empty/unparseable, keep the last successfully loaded user config (like #7723 proposes for parse failures) instead of falling back tobuiltinShellConfig— and never persist that fallback. - In
mutateShellConfig/updateEntryInline, refuse to persist a config that is byte-identical to the shipped defaults when the last-ingested user config was not; log it. - Treat a mid-session empty/truncated file as "don't reload into defaults" — retain the in-memory
shellConfiguntil a non-empty valid file arrives.
System
- Omarchy 4.0.0.r2186.gee8ebf6-1 (edge), quickshell 0.3.1, Hyprland 0.56.2, Arch
- ASUS ExpertBook PM3406CKA
Related: #11349 (same fallback, startup-window trigger), #7723 (keep-last-good on parse failure), #10885 (mutateShellConfig whole-file rewrite hazard).
View raw text
### What happens
When `~/.config/omarchy/shell.json` becomes empty (0 bytes) while the shell is running, the bar reverts to the stock layout and every third-party plugin is disabled — **permanently**, because the very next shell-side config write persists the shipped defaults over the user's file. No backup is written and nothing is logged.
This is the same `mutateShellConfig` fallback as #11349, but with a different trigger: #11349 is the startup window (a mutation landing before the async `FileView` load completes). This one is a **runtime truncation** — the user's file is already loaded and fine, then it becomes empty, and the shell turns that into a permanent reset.
### Why
The truncation reaches the fallback through the `FileView` change/reload path:
```qml
// shell.qml
FileView {
id: userConfigFile
path: shell.userConfigPath
watchChanges: true
atomicWrites: true
onLoaded: shell.applyShellConfig()
onLoadFailed: function(error) { shell.applyShellConfig() }
onFileChanged: reload() // line 142
}
```
When the file changes (including becoming 0 bytes), `reload()` → `onLoaded` → `applyShellConfig()`:
```qml
// shell.qml:73-88
function applyShellConfig() {
var defaults = Util.isPlainObject(defaultsConfig) ? defaultsConfig : builtinShellConfig
var user = null
var userText = userConfigFile.text() || ""
if (userText.trim()) {
// parse; set user
}
shellConfig = user || defaults // empty file → user null → builtinShellConfig (stock)
}
```
`shellConfig` is now the **builtin stock config**. The next plugin-initiated write — idle service, a widget persisting a setting, OmaSettings, `omarchy bar set …` — goes through `mutateShellConfig()`:
```qml
// shell.qml:162-166 (also 1069 in updateEntryInline)
function mutateShellConfig(mutator) {
var copy = JSON.parse(JSON.stringify(shellConfig || builtinShellConfig))
mutator(copy)
persistShellConfig(copy) // writes the whole file — stock layout + plugins: []
}
```
…which writes the stock config back to disk. A transient truncation is now a permanent reset, and because enablement is stored in shell.json, every third-party plugin drops off the bar at once.
### Evidence
On an edge-channel install I've seen this ~14 times across a week. A path-unit guard I run snapshots and restores the file, so the timeline is recorded:
- 14 × `shell.json.bak.EMPTY.*` artifacts — the 0-byte truncations (Sep 16–19).
- Guard log: after each truncation the file came back within ~2 s as a ~1370-byte config — the shipped default, not the user's (2312–2351-byte) config:
```
2026-09-21 21:04:50 was mid-write (1372 bytes) — snapshot updated
2026-09-22 20:37:01 was mid-write (1374 bytes) — snapshot updated
```
- After restoring the user's config on Sep 21 22:55, the file was rewritten to stock again at 22:57 (`shell.json.bak.RESET.20260921225710`, 1374 bytes) — a settings change landing after a truncation, persisting the defaults.
The spontaneous 0-byte truncation itself is a separate, still-unexplained bug (every occurrence lands within the minute of a shell config reload, often right after an `omarchy update`; the shell's own `FileView.setText` with `atomicWrites: true` is the only non-atomic writer involved). But even if that trigger is never fixed, the shell should not *amplify* it into a permanent reset.
### Reproduce
On a machine with a non-default layout (a third-party plugin in the bar, anything in `plugins[]`):
1. While the shell is running: `: > ~/.config/omarchy/shell.json`
2. Trigger any shell-side config write, e.g. `omarchy bar set omarchy.clock format "dddd HH:mm"` (or wait for the idle service / a widget to persist a setting).
3. `~/.config/omarchy/shell.json` is now byte-for-byte the shipped default plus that one change; the user's layout and every third-party plugin are gone, with no backup and no log line.
### Suggested fixes
Any one of these would stop the amplifier (independent of, and complementary to, #11349):
- In `applyShellConfig`, when the user file is empty/unparseable, keep the **last successfully loaded user config** (like #7723 proposes for parse failures) instead of falling back to `builtinShellConfig` — and never persist that fallback.
- In `mutateShellConfig` / `updateEntryInline`, refuse to persist a config that is byte-identical to the shipped defaults when the last-ingested user config was not; log it.
- Treat a mid-session empty/truncated file as "don't reload into defaults" — retain the in-memory `shellConfig` until a non-empty valid file arrives.
### System
- Omarchy 4.0.0.r2186.gee8ebf6-1 (edge), quickshell 0.3.1, Hyprland 0.56.2, Arch
- ASUS ExpertBook PM3406CKA
Related: #11349 (same fallback, startup-window trigger), #7723 (keep-last-good on parse failure), #10885 (`mutateShellConfig` whole-file rewrite hazard).
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 17:32 UTC