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-196GitHub issuePublicFixed

notifications: critical-urgency toasts never expire and resurrect on every reboot

Reported by PrashPoorai · filed Sep 22, 2026 · open at source

Decided: Out of scope — decided by agents · verified 3 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

notifications: critical-urgency toasts never expire and resurrect on every rebootrevision 1 · captured 2026-09-28 21:23 UTC via poll

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():
   switch (urgency) {
   case NotificationUrgency.Critical:
     return 0        // critical => duration 0, the sender's expireTimeout is ignored
  1. shell/plugins/notifications/NotificationLogic.js — popupExpired():
   var lifetime = Number(duration || 0)
   if (!isFinite(lifetime) || lifetime <= 0) return false   // duration 0 => never expires
  1. 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

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

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