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.
notifications: critical-urgency toasts never expire and resurrect on every reboot
Reported by PrashPoorai · filed Sep 22, 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
Critical-urgency toasts never expire and resurrect on every reboot
Summary
A notification sent with critical urgency and a non-zero expireTimeout is
treated as immortal by the shell's notification plugin: it never expires on
screen, its state file is never archived, and it is re-shown (with a fresh
lifetime) after every reboot — indefinitely, until manually dismissed.
Version / system
- Omarchy 4.0.4-1
- Kernel 7.2.5-3-omarchy, Arch base
- Lenovo Legion 5 (i9-13900H, RTX 4070 laptop), Hyprland
What happened
A "Time to recharge!" low-battery toast (sent by omarchy battery low, which
uses omarchy-notification-send -u critical -t 30000) appeared on the evening
of Sep 21. The machine was rebooted several times over the next ~18 hours;
the toast reappeared on every boot, still in the top-right corner, never
expiring. It could only be removed with omarchy notification dismiss
(the summary substring had to match exactly) or by deleting its state file.
Root cause (traced in shell source)
The three pieces that combine into the bug:
shell/plugins/notifications/Service.qml—durationFor():
switch (urgency) {
case NotificationUrgency.Critical:
return 0 // critical => duration 0, the sender's expireTimeout is ignoredshell/plugins/notifications/NotificationLogic.js—popupExpired():
var lifetime = Number(duration || 0)
if (!isFinite(lifetime) || lifetime <= 0) return false // duration 0 => never expiresService.qml—restorePopups(): entries that "haven't expired" are
restored at startup, and survivors "restart with a full lifetime on
purpose". With duration 0 there is no deadline at all, so the toast is
re-shown on every boot with no expiry — permanently.
Per the FreeDesktop notification spec, expireTimeout == 0 means "never
expire"; a non-zero expireTimeout should be honoured regardless of
urgency. The shell currently maps critical → 0 unconditionally, so the
sender's explicit 30000 ms request is discarded.
Note the design comment in restorePopups() shows the intent — critical
alerts must not be silently dropped across restarts. The bug is that a
critical toast that did request a finite timeout gets an infinite one.
Repro
omarchy notification send -u critical -t 30000 "Repro" "critical + 30s timeout"
reboot # or: omarchy restart shell- The toast reappears after the restart and never expires.
- The state file persists in
~/.local/state/omarchy/notifications/<timestamp>-<id>.json and is
re-restored on every subsequent boot.
Minimal persisted file from the real incident (18 h old, still live):
{"id":1,"originalId":1,"app":"omarchy-action","appIcon":"battery-caution","summary":"Time to recharge!","body":"Battery is down to 10%","image":"","glyph":"","execArgv":"","urgency":2,"expireTimeout":30000,"timestamp":1790021406967}Expected
Either:
- honour the sender's non-zero
expireTimeouteven for critical urgency
(spec-compliant), or
- at minimum, don't restore a persisted critical toast whose original
lifetime has clearly already elapsed (e.g. judge it against
timestamp + expireTimeout when no reset deadline is present).
Impact
- Stale toasts accumulate across reboots and keep occupying the top-right
corner indefinitely (the incident toast blocked part of the bar area for
~18 h across 3+ boots).
- Critical toasts also bypass DND, so a stuck one can't be suppressed that
way either.
Attachments
Screenshot of a repro toast (critical urgency + 30s timeout) on screen —
attached below.
View raw text
# Critical-urgency toasts never expire and resurrect on every reboot
## Summary
A notification sent with critical urgency and a non-zero `expireTimeout` is
treated as immortal by the shell's notification plugin: it never expires on
screen, its state file is never archived, and it is re-shown (with a fresh
lifetime) after every reboot — indefinitely, until manually dismissed.
## Version / system
- Omarchy 4.0.4-1
- Kernel 7.2.5-3-omarchy, Arch base
- Lenovo Legion 5 (i9-13900H, RTX 4070 laptop), Hyprland
## What happened
A "Time to recharge!" low-battery toast (sent by `omarchy battery low`, which
uses `omarchy-notification-send -u critical -t 30000`) appeared on the evening
of Sep 21. The machine was rebooted several times over the next ~18 hours;
the toast reappeared on **every** boot, still in the top-right corner, never
expiring. It could only be removed with `omarchy notification dismiss`
(the summary substring had to match exactly) or by deleting its state file.
## Root cause (traced in shell source)
The three pieces that combine into the bug:
1. `shell/plugins/notifications/Service.qml` — `durationFor()`:
```js
switch (urgency) {
case NotificationUrgency.Critical:
return 0 // critical => duration 0, the sender's expireTimeout is ignored
```
2. `shell/plugins/notifications/NotificationLogic.js` — `popupExpired()`:
```js
var lifetime = Number(duration || 0)
if (!isFinite(lifetime) || lifetime <= 0) return false // duration 0 => never expires
```
3. `Service.qml` — `restorePopups()`: entries that "haven't expired" are
restored at startup, and survivors "restart with a full lifetime on
purpose". With duration 0 there is no deadline at all, so the toast is
re-shown on every boot with no expiry — permanently.
Per the FreeDesktop notification spec, `expireTimeout == 0` means "never
expire"; a **non-zero** `expireTimeout` should be honoured regardless of
urgency. The shell currently maps critical → 0 unconditionally, so the
sender's explicit 30000 ms request is discarded.
Note the design comment in `restorePopups()` shows the intent — critical
alerts must not be *silently dropped* across restarts. The bug is that a
critical toast that *did* request a finite timeout gets an infinite one.
## Repro
```bash
omarchy notification send -u critical -t 30000 "Repro" "critical + 30s timeout"
reboot # or: omarchy restart shell
```
- The toast reappears after the restart and never expires.
- The state file persists in
`~/.local/state/omarchy/notifications/<timestamp>-<id>.json` and is
re-restored on every subsequent boot.
Minimal persisted file from the real incident (18 h old, still live):
```json
{"id":1,"originalId":1,"app":"omarchy-action","appIcon":"battery-caution","summary":"Time to recharge!","body":"Battery is down to 10%","image":"","glyph":"","execArgv":"","urgency":2,"expireTimeout":30000,"timestamp":1790021406967}
```
## Expected
Either:
- honour the sender's non-zero `expireTimeout` even for critical urgency
(spec-compliant), or
- at minimum, don't restore a persisted critical toast whose original
lifetime has clearly already elapsed (e.g. judge it against
`timestamp + expireTimeout` when no reset deadline is present).
## Impact
- Stale toasts accumulate across reboots and keep occupying the top-right
corner indefinitely (the incident toast blocked part of the bar area for
~18 h across 3+ boots).
- Critical toasts also bypass DND, so a stuck one can't be suppressed that
way either.
## Attachments
Screenshot of a repro toast (critical urgency + 30s timeout) on screen —
attached below.
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
- Fixed bypr/9790· confirmed
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 19:29 UTC