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-2092GitHub issuePublicDecided· Out of scope

Launch feedback OSD can get stuck on screen indefinitely (duration: 0 with no fallback closer)

Reported by Tiedeyez · filed Sep 29, 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

Launch feedback OSD can get stuck on screen indefinitely (duration: 0 with no fallback closer)revision 1 · captured 2026-09-29 03:09 UTC via poll

Summary

The launch feedback OSD ("Launching app…") can get stuck on screen indefinitely. It opened at 20:29 and was still displayed at 20:45 — roughly 16 minutes — until dismissed manually with omarchy-shell osd close.

The OSD is deliberately opened with duration: 0, so it has no hide timer of its own. closeLaunchFeedback() is the only thing that takes it down, and it appears to have never run.

Steps to reproduce

  1. Launch an app whose startup is slow enough that its first toplevel does not exist ~2 s after the launch is requested. On this machine, Chromium cold start took 11 s, which is enough.
  2. Launch it from the shell (menu / launcher), not from a terminal — the terminal path does not go through AppLibrary.launch(), so no OSD is created and the bug cannot trigger.
  3. Wait for the app window to appear.
  4. The OSD remains on screen indefinitely.

Observed timeline from the user journal:

20:29:00  uwsm_app-daemon: received: app -- gtk-launch chromium.desktop
20:29:00  systemd: Started gtk-launch.
20:29:11  systemd: Started app-org.chromium.Chromium-26526.scope.   <- 11 s to first window
20:43:56  app-org.chromium.Chromium-26526.scope: Consumed 6min 12s CPU, 2.1G memory peak

The OSD was still present when observed at ~20:45.

What I confirmed

The OSD was genuinely stuck open, not a rendering artifact:

$ quickshell -p /usr/share/omarchy/shell ipc call osd state
open

An OSD opened with `duration: 0` never dismisses itself. This is by design — Osd.qml only starts hideTimer when duration > 0:

// Osd.qml
opened = true
if (duration > 0) hideTimer.restart()
else hideTimer.stop()

Verified directly. With no external closer, the OSD stayed open for the full 30 s observation window and had to be closed by hand:

$ omarchy-shell osd show '{"icon":"","message":"ORPHAN TEST","duration":0}'
t+05s: open
t+10s: open
...
t+30s: open
$ omarchy-shell osd close
$ quickshell -p /usr/share/omarchy/shell ipc call osd state
closed

The manual close path works. omarchy-shell is on the shell's PATH (/usr/share/omarchy/bin is in /proc/953/environ), and the same IPC call that closeLaunchFeedback() uses succeeds when invoked by hand.

This is not theme-related. The active theme ships no [osd] section in shell.toml, ~/.config/omarchy/plugins/ is empty, and no user QML references osd or launchOsd. The whole code path is stock.

The shell was never restarted during the incident — the quickshell process had been up continuously since 16:41:48 — so the stuck state survived in-process rather than being wiped by a reload.

Where I think the bug is

AppLibrary.qml opens the OSD with no self-healing backstop:

// AppLibrary.qml:245
Quickshell.execDetached(["omarchy-shell", "osd", "show",
  JSON.stringify({ icon: "󱓞", message: root.launchOsdMessage, duration: 0 })])

and clears it only from two places:

// AppLibrary.qml:250
Timer {
  id: launchTimeout
  interval: 15000
  onTriggered: root.closeLaunchFeedback(root.launchSerial)
}
// AppLibrary.qml:180
function maybeFinishLaunchFeedback() {
  if (!launchDelay.running && !launchTimeout.running && !root.launchOsdOpen) return
  if (root.toplevelCount() <= root.launchToplevelCount
      && ToplevelManager.activeToplevel === root.launchActiveToplevel) return
  root.closeLaunchFeedback(root.launchSerial)
}

One of those two must have failed. I was not able to distinguish which:

  1. The 15 s `launchTimeout` never fired. The QML event loop was alive (the shell answered ping and state throughout, and the OSD was interactive), so a plain missed Timer is odd, but I could not rule it out.
  2. The `ToplevelManager` change signal never reached `AppLibrary.qml`. maybeFinishLaunchFeedback is wired to onValuesChanged of ToplevelManager.toplevels and to onActiveToplevelChanged. If neither fired for Chromium's window, the early return on the toplevel-count comparison would keep the OSD up.

The design issue, independent of which one it is

A duration: 0 OSD guarded by a single closer and no fallback is fragile by construction. Any transient failure in the closer — a dropped signal, a timer that does not fire, an execDetached that fails silently — leaves an undismissable overlay on screen with no way for the shell to recover on its own. The only thing that clears it is a manual omarchy-shell osd close.

Suggested hardening, in rough order of preference:

  • Give the launch OSD a bounded duration (it is a progress hint, not a persistent status) so it always self-clears.
  • Or keep duration: 0 but add a second, independent watchdog so a missed signal cannot strand it.
  • Or have AppLibrary.qml reconcile on focus/Component.onCompleted, so a stranded OSD is torn down the next time the shell gains awareness of it.

I am not claiming a specific one of these is the fix — the point is that the failure should not be able to persist.

Workaround

omarchy-shell osd close

System

Omarchy4.0.4-1
Kernel7.2.5-3-omarchy
Hyprland0.56.2
Quickshell0.3.1-1
Chromium152.0.7977.82-1
CPUIntel Core i3-6100U @ 2.30GHz (2 cores / 4 threads, powersave)
GPUIntel Skylake-U GT2 [HD Graphics 520]
MachineHP Notebook (W2M98UA#ABA)
RAM5.7 GiB

This is a low-powered 2015 laptop. If the failure turns out to be a QML timer being starved under load, it may be much harder to hit on faster hardware — but the missing backstop is a real hazard either way.

Full diagnostics: /tmp/omarchy-debug.log (generated with omarchy-debug --no-sudo --print).

Notes on this report

Two caveats worth stating plainly rather than burying:

  • I could not reproduce the failure on demand. My attempt to trigger it by launching Chromium through gtk-launch directly never created an OSD at all, because that path bypasses AppLibrary.launch() and therefore beginLaunchFeedback(). The bug needs a launch from the shell UI, and I did not have a way to drive that unattended. The evidence above is therefore a confirmed single occurrence plus a confirmed mechanism, not a confirmed reproduction. Steps to reproduce are written from the code path, not from a second confirmed run.
  • I could not search for duplicates. The GitHub search API returns HTTP 422 for this repo from my account, so I have not ruled out an existing issue covering this. If one exists, the mechanism details above are probably still worth folding into it.
View raw text
## Summary

The launch feedback OSD ("Launching *app*…") can get stuck on screen indefinitely. It opened at 20:29 and was still displayed at 20:45 — roughly 16 minutes — until dismissed manually with `omarchy-shell osd close`.

The OSD is deliberately opened with `duration: 0`, so it has no hide timer of its own. `closeLaunchFeedback()` is the only thing that takes it down, and it appears to have never run.

## Steps to reproduce

1. Launch an app whose startup is slow enough that its first toplevel does not exist ~2 s after the launch is requested. On this machine, Chromium cold start took 11 s, which is enough.
2. Launch it from the shell (menu / launcher), not from a terminal — the terminal path does not go through `AppLibrary.launch()`, so no OSD is created and the bug cannot trigger.
3. Wait for the app window to appear.
4. The OSD remains on screen indefinitely.

Observed timeline from the user journal:

```
20:29:00  uwsm_app-daemon: received: app -- gtk-launch chromium.desktop
20:29:00  systemd: Started gtk-launch.
20:29:11  systemd: Started app-org.chromium.Chromium-26526.scope.   <- 11 s to first window
20:43:56  app-org.chromium.Chromium-26526.scope: Consumed 6min 12s CPU, 2.1G memory peak
```

The OSD was still present when observed at ~20:45.

## What I confirmed

**The OSD was genuinely stuck open**, not a rendering artifact:

```console
$ quickshell -p /usr/share/omarchy/shell ipc call osd state
open
```

**An OSD opened with `duration: 0` never dismisses itself.** This is by design — `Osd.qml` only starts `hideTimer` when `duration > 0`:

```qml
// Osd.qml
opened = true
if (duration > 0) hideTimer.restart()
else hideTimer.stop()
```

Verified directly. With no external closer, the OSD stayed open for the full 30 s observation window and had to be closed by hand:

```console
$ omarchy-shell osd show '{"icon":"","message":"ORPHAN TEST","duration":0}'
t+05s: open
t+10s: open
...
t+30s: open
$ omarchy-shell osd close
$ quickshell -p /usr/share/omarchy/shell ipc call osd state
closed
```

**The manual close path works.** `omarchy-shell` is on the shell's `PATH` (`/usr/share/omarchy/bin` is in `/proc/953/environ`), and the same IPC call that `closeLaunchFeedback()` uses succeeds when invoked by hand.

**This is not theme-related.** The active theme ships no `[osd]` section in `shell.toml`, `~/.config/omarchy/plugins/` is empty, and no user QML references `osd` or `launchOsd`. The whole code path is stock.

**The shell was never restarted** during the incident — the `quickshell` process had been up continuously since 16:41:48 — so the stuck state survived in-process rather than being wiped by a reload.

## Where I think the bug is

`AppLibrary.qml` opens the OSD with no self-healing backstop:

```qml
// AppLibrary.qml:245
Quickshell.execDetached(["omarchy-shell", "osd", "show",
  JSON.stringify({ icon: "󱓞", message: root.launchOsdMessage, duration: 0 })])
```

and clears it only from two places:

```qml
// AppLibrary.qml:250
Timer {
  id: launchTimeout
  interval: 15000
  onTriggered: root.closeLaunchFeedback(root.launchSerial)
}
```

```qml
// AppLibrary.qml:180
function maybeFinishLaunchFeedback() {
  if (!launchDelay.running && !launchTimeout.running && !root.launchOsdOpen) return
  if (root.toplevelCount() <= root.launchToplevelCount
      && ToplevelManager.activeToplevel === root.launchActiveToplevel) return
  root.closeLaunchFeedback(root.launchSerial)
}
```

One of those two must have failed. I was not able to distinguish which:

1. **The 15 s `launchTimeout` never fired.** The QML event loop was alive (the shell answered `ping` and `state` throughout, and the OSD was interactive), so a plain missed `Timer` is odd, but I could not rule it out.
2. **The `ToplevelManager` change signal never reached `AppLibrary.qml`.** `maybeFinishLaunchFeedback` is wired to `onValuesChanged` of `ToplevelManager.toplevels` and to `onActiveToplevelChanged`. If neither fired for Chromium's window, the early `return` on the toplevel-count comparison would keep the OSD up.

### The design issue, independent of which one it is

A `duration: 0` OSD guarded by a single closer and no fallback is fragile by construction. Any transient failure in the closer — a dropped signal, a timer that does not fire, an `execDetached` that fails silently — leaves an undismissable overlay on screen with no way for the shell to recover on its own. The only thing that clears it is a manual `omarchy-shell osd close`.

Suggested hardening, in rough order of preference:

- Give the launch OSD a bounded duration (it is a progress hint, not a persistent status) so it always self-clears.
- Or keep `duration: 0` but add a second, independent watchdog so a missed signal cannot strand it.
- Or have `AppLibrary.qml` reconcile on focus/`Component.onCompleted`, so a stranded OSD is torn down the next time the shell gains awareness of it.

I am not claiming a specific one of these is the fix — the point is that the failure should not be able to persist.

## Workaround

```bash
omarchy-shell osd close
```

## System

| | |
|---|---|
| Omarchy | 4.0.4-1 |
| Kernel | 7.2.5-3-omarchy |
| Hyprland | 0.56.2 |
| Quickshell | 0.3.1-1 |
| Chromium | 152.0.7977.82-1 |
| CPU | Intel Core i3-6100U @ 2.30GHz (2 cores / 4 threads, `powersave`) |
| GPU | Intel Skylake-U GT2 [HD Graphics 520] |
| Machine | HP Notebook (W2M98UA#ABA) |
| RAM | 5.7 GiB |

This is a low-powered 2015 laptop. If the failure turns out to be a QML timer being starved under load, it may be much harder to hit on faster hardware — but the missing backstop is a real hazard either way.

Full diagnostics: `/tmp/omarchy-debug.log` (generated with `omarchy-debug --no-sudo --print`).

## Notes on this report

Two caveats worth stating plainly rather than burying:

- **I could not reproduce the failure on demand.** My attempt to trigger it by launching Chromium through `gtk-launch` directly never created an OSD at all, because that path bypasses `AppLibrary.launch()` and therefore `beginLaunchFeedback()`. The bug needs a launch from the shell UI, and I did not have a way to drive that unattended. The evidence above is therefore a confirmed single occurrence plus a confirmed mechanism, not a confirmed reproduction. Steps to reproduce are written from the code path, not from a second confirmed run.
- **I could not search for duplicates.** The GitHub search API returns HTTP 422 for this repo from my account, so I have not ruled out an existing issue covering this. If one exists, the mechanism details above are probably still worth folding into it.

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 20:07 UTC