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.
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
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
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
- 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.
- 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. - Wait for the app window to appear.
- 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 peakThe 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
openAn 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
closedThe 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:
- The 15 s `launchTimeout` never fired. The QML event loop was alive (the shell answered
pingandstatethroughout, and the OSD was interactive), so a plain missedTimeris odd, but I could not rule it out. - The `ToplevelManager` change signal never reached `AppLibrary.qml`.
maybeFinishLaunchFeedbackis wired toonValuesChangedofToplevelManager.toplevelsand toonActiveToplevelChanged. If neither fired for Chromium's window, the earlyreturnon 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: 0but add a second, independent watchdog so a missed signal cannot strand it. - Or have
AppLibrary.qmlreconcile 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 closeSystem
| 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-launchdirectly never created an OSD at all, because that path bypassesAppLibrary.launch()and thereforebeginLaunchFeedback(). 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