Proof Portal

Project overview

Omarchy

ProbeLabs65 findings · 95 requirements

A 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.

Inbox
ReportR-1284GitHub issuePublicDecided· Out of scope

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

Decided: Out of scope — decided by agents · verified 5 days ago.

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-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 tracerevision 1 · captured 2026-09-28 21:24 UTC via poll

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 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:

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:

calldocumented asmeasured
omarchy-shell notifications ping—2.00 s
omarchy-notification-wait 11 second21.03 s
omarchy-notification-wait (default — how crash-watch calls it)10 seconds210.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

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:
   omarchy-notification-wait || {
     echo "crash-watch: notification server unavailable, dropped crash for $comm" >&2
     return 1
   }
  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
  1. 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
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