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.
omarchy-notification-wait treats its timeout argument as an attempt count: a crash can block the watcher for 3.5 minutes and then be dropped with no trace
Reported by Dandroid-D · filed Aug 29, 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
omarchy-crash-watch calls omarchy-notification-wait before every toast (bin/omarchy-crash-watch:33). That helper's argument is documented as [timeout-seconds], but it is used as an attempt count, and each attempt is bounded only by omarchy-shell's own IPC timeout. The wait ends up ~21× longer than the parameter reads, it is taken synchronously inside the loop that consumes crashes, and it ends in a silent drop.
The arithmetic
bin/omarchy-notification-wait:
timeout=${1:-10}
attempts=$((timeout * 10))
while (( attempts > 0 )); do
if omarchy-shell notifications ping >/dev/null 2>&1 && notification_server_ready; then
exit 0
fi
attempts=$((attempts - 1))
sleep 0.1
done
exit 1timeout * 10 iterations of sleep 0.1 equal timeout seconds only if each ping is instant. It isn't — bin/omarchy-shell wraps it:
ipc_timeout=${OMARCHY_SHELL_IPC_TIMEOUT:-2s}
output=$(timeout --kill-after=1s "$ipc_timeout" qs ipc -n -p "$OMARCHY_PATH/shell" call -- "$@" 2>/dev/null)So when quickshell's IPC accepts but never answers, one attempt costs 2s, not ~0s.
Measured on omarchy 4.0.1-1 against a stub qs that accepts and never replies:
| call | documented as | measured |
|---|---|---|
omarchy-shell notifications ping | — | 2.00 s |
omarchy-notification-wait 1 | 1 second | 21.03 s |
omarchy-notification-wait (default — how crash-watch calls it) | 10 seconds | 210.3 s (3.5 min) |
210.3 s = 100 attempts × (2.0 s + 0.1 s). If qs ignores SIGTERM, --kill-after=1s adds ~1 s per attempt, giving ~310 s.
Why it matters
announce() runs synchronously in the loop that reads crashes (bin/omarchy-crash-watch:85), so for those minutes the watcher consumes nothing.
The worst case is the case the wait was written for. The comment at line 29 is right that a shell crash takes the notification server down with it — but quickshell is not in the mute list at line 77 (only omarchy-crash-* and omarchy-agent-*), so a shell crash is itself announced, and that announcement is the one that enters the long wait. A shell that crashes and doesn't come straight back blocks the watcher for minutes, and every other crash in that window is missed.
The failure is also invisible:
omarchy-notification-waitwrites nothing to stderr; it justexit 1.- Line 33 is
omarchy-notification-wait || return 1— noelse, no log. - Line 85 is
announce ... && last_notified[$name]=$now— the false branch does nothing. - The unit stays
active.
The observable result: a process crashed, no toast appeared, nothing was written to any log, and systemctl --user is-active omarchy-crash-watch still says active. There's no way to tell afterwards that it happened.
Missed crashes aren't recoverable either — journalctl -f -n 0 (line 49) doesn't replay. The inotify rewrite in #7155 deliberately keeps that property ("core files already on disk are not replayed").
Reproduction
mkdir -p /tmp/stub && printf '#!/bin/bash\nsleep 30\n' > /tmp/stub/qs && chmod +x /tmp/stub/qs
time PATH=/tmp/stub:$PATH omarchy-notification-wait 1 # ~21s, not 1s
time PATH=/tmp/stub:$PATH omarchy-notification-wait # ~210s, not 10sEnd to end: PATH-shadow omarchy-shell with a stub that stalls ~3 s inside a transient unit running the packaged omarchy-crash-watch, then induce a core dump. The journal entry reaches the watcher, no toast appears, the unit journal records nothing, and the unit stays active.
What this does not show
I haven't shown this is the cause of any particular missed notification in the wild — it's a mechanism reproducible on demand, not a diagnosis. Sampling the gate on an otherwise healthy machine (1440 samples over ~13.5 h) produced zero failures, with ping p50 16.4 ms against the 2 s budget and a max of 243 ms. On a healthy system the gate is nowhere near its limit. The concern is specifically the shell-down window — which is exactly when the gate is exercised, and exactly when it is slowest.
Suggested fixes
announce() and omarchy-notification-wait are untouched by #7155, so these apply either way.
- Log the drop. One line turns a silent loss into something greppable:
omarchy-notification-wait || {
echo "crash-watch: notification server unavailable, dropped crash for $comm" >&2
return 1
}- Make the budget mean what it says — bound the helper by wall clock rather than attempt count, or document the argument as an attempt count:
deadline=$((SECONDS + timeout))
while (( SECONDS < deadline )); do- Optionally, keep the announce wait off the consumer loop, so waiting for the shell to return doesn't stop the watcher reading later crashes.
Environment
- omarchy 4.0.1-1
- Reproduced against unmodified packaged
/usr/bin/omarchy-crash-watch,/usr/bin/omarchy-notification-wait,/usr/share/omarchy/bin/omarchy-shell systemd-coredumpwith the defaultStorage=external
View raw text
`omarchy-crash-watch` calls `omarchy-notification-wait` before every toast (`bin/omarchy-crash-watch:33`). That helper's argument is documented as `[timeout-seconds]`, but it is used as an *attempt count*, and each attempt is bounded only by `omarchy-shell`'s own IPC timeout. The wait ends up ~21× longer than the parameter reads, it is taken synchronously inside the loop that consumes crashes, and it ends in a silent drop.
## The arithmetic
`bin/omarchy-notification-wait`:
```bash
timeout=${1:-10}
attempts=$((timeout * 10))
while (( attempts > 0 )); do
if omarchy-shell notifications ping >/dev/null 2>&1 && notification_server_ready; then
exit 0
fi
attempts=$((attempts - 1))
sleep 0.1
done
exit 1
```
`timeout * 10` iterations of `sleep 0.1` equal `timeout` seconds only if each ping is instant. It isn't — `bin/omarchy-shell` wraps it:
```bash
ipc_timeout=${OMARCHY_SHELL_IPC_TIMEOUT:-2s}
output=$(timeout --kill-after=1s "$ipc_timeout" qs ipc -n -p "$OMARCHY_PATH/shell" call -- "$@" 2>/dev/null)
```
So when quickshell's IPC accepts but never answers, one attempt costs 2s, not ~0s.
Measured on omarchy 4.0.1-1 against a stub `qs` that accepts and never replies:
| call | documented as | measured |
|---|---|---|
| `omarchy-shell notifications ping` | — | 2.00 s |
| `omarchy-notification-wait 1` | 1 second | **21.03 s** |
| `omarchy-notification-wait` (default — how crash-watch calls it) | 10 seconds | **210.3 s (3.5 min)** |
210.3 s = 100 attempts × (2.0 s + 0.1 s). If `qs` ignores `SIGTERM`, `--kill-after=1s` adds ~1 s per attempt, giving ~310 s.
## Why it matters
`announce()` runs synchronously in the loop that reads crashes (`bin/omarchy-crash-watch:85`), so for those minutes the watcher consumes nothing.
The worst case is the case the wait was written for. The comment at line 29 is right that a shell crash takes the notification server down with it — but quickshell is not in the mute list at line 77 (only `omarchy-crash-*` and `omarchy-agent-*`), so a shell crash is itself announced, and that announcement is the one that enters the long wait. A shell that crashes and doesn't come straight back blocks the watcher for minutes, and every other crash in that window is missed.
The failure is also invisible:
- `omarchy-notification-wait` writes nothing to stderr; it just `exit 1`.
- Line 33 is `omarchy-notification-wait || return 1` — no `else`, no log.
- Line 85 is `announce ... && last_notified[$name]=$now` — the false branch does nothing.
- The unit stays `active`.
The observable result: a process crashed, no toast appeared, nothing was written to any log, and `systemctl --user is-active omarchy-crash-watch` still says `active`. There's no way to tell afterwards that it happened.
Missed crashes aren't recoverable either — `journalctl -f -n 0` (line 49) doesn't replay. The inotify rewrite in #7155 deliberately keeps that property ("core files already on disk are not replayed").
## Reproduction
```bash
mkdir -p /tmp/stub && printf '#!/bin/bash\nsleep 30\n' > /tmp/stub/qs && chmod +x /tmp/stub/qs
time PATH=/tmp/stub:$PATH omarchy-notification-wait 1 # ~21s, not 1s
time PATH=/tmp/stub:$PATH omarchy-notification-wait # ~210s, not 10s
```
End to end: PATH-shadow `omarchy-shell` with a stub that stalls ~3 s inside a transient unit running the packaged `omarchy-crash-watch`, then induce a core dump. The journal entry reaches the watcher, no toast appears, the unit journal records nothing, and the unit stays `active`.
## What this does not show
I haven't shown this is the cause of any particular missed notification in the wild — it's a mechanism reproducible on demand, not a diagnosis. Sampling the gate on an otherwise healthy machine (1440 samples over ~13.5 h) produced zero failures, with ping p50 16.4 ms against the 2 s budget and a max of 243 ms. On a healthy system the gate is nowhere near its limit. The concern is specifically the shell-down window — which is exactly when the gate is exercised, and exactly when it is slowest.
## Suggested fixes
`announce()` and `omarchy-notification-wait` are untouched by #7155, so these apply either way.
1. **Log the drop.** One line turns a silent loss into something greppable:
```bash
omarchy-notification-wait || {
echo "crash-watch: notification server unavailable, dropped crash for $comm" >&2
return 1
}
```
2. **Make the budget mean what it says** — bound the helper by wall clock rather than attempt count, or document the argument as an attempt count:
```bash
deadline=$((SECONDS + timeout))
while (( SECONDS < deadline )); do
```
3. Optionally, keep the announce wait off the consumer loop, so waiting for the shell to return doesn't stop the watcher reading later crashes.
## Environment
- omarchy 4.0.1-1
- Reproduced against unmodified packaged `/usr/bin/omarchy-crash-watch`, `/usr/bin/omarchy-notification-wait`, `/usr/share/omarchy/bin/omarchy-shell`
- `systemd-coredump` with the default `Storage=external`
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 09:50 UTC