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 OSD ("Launching <app>…") can get permanently stuck on screen
Reported by reyalicea · filed Aug 28, 2026Closed upstream on 2026-09-21 by bjarneo via an automated message, as a duplicate of #8242 (R-1462). GitHub close reason: completed.
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?
- Not yet mapped to any requirement or component.
- 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?
- Our staff decide whether the report is valid. Where this project allows it, agents decide whether it duplicates another report or a known issue. No decision has been recorded yet.
Original report
Launch OSD ("Launching <app>…") can get permanently stuck on screen
What happened
After launching an app from the Omarchy menu, the persistent "Launching <app>…"
OSD overlay stayed on screen indefinitely. It never cleared on its own. Theomarchy-osd layer-shell surface remained up (visible card, duration: 0),
and omarchy-shell osd close was required to dismiss it manually.
What was expected
The launch OSD should always clear — either when the launched window appears,
or via the 15s timeout fallback — with no path that leaves it stranded.
Where it comes from
shell/services/AppLibrary.qml:
beginLaunchFeedback(name)startslaunchDelay(2000 ms) and
launchTimeout (15000 ms).
- launchDelay.onTriggered sets launchOsdOpen = true and calls
`Quickshell.execDetached(["omarchy-shell", "osd", "show",
{icon, message, duration: 0}])`.
- closeLaunchFeedback() calls
Quickshell.execDetached(["omarchy-shell", "osd", "close"]).
shell/plugins/osd/Osd.qml: show() with duration <= 0 callshideTimer.stop() and leaves opened = true until an explicit close().
There is no self-heal / no max lifetime for a duration: 0 OSD.
Root cause (two compounding issues)
- Show/close race.
showandcloseare dispatched as two *separate
detached* omarchy-shell processes. Each then makes its own qs ipc call
to the shell. Delivery order is decided by process scheduling, not by the
order the QML issued them. When a toplevel appears in roughly the same tick
the 2s launchDelay fires, closeLaunchFeedback() runs right after
launchDelay.onTriggered, so a close IPC can reach the shell before the
show IPC. Net result: opened ends up true with no timer, forever.
- Single-shot backstop with no retry. For a launch that never produces a
detectable new/active toplevel (app already running and merely refocused so
ToplevelManager.toplevels count and activeToplevel are unchanged, a
background/agent app, or a failed launch), the only thing that clears the
OSD is the one-shot launchTimeout → single closeLaunchFeedback(). If
that lone detached close fails to land (shell busy past the 2s
OMARCHY_SHELL_IPC_TIMEOUT, transient error), nothing ever retries and the
OSD is stranded.
Steps to reproduce
Exact timing is racy, but the shape:
- From the Omarchy menu, launch an app whose window takes ~2s to map, or an
app that is already open and just gets refocused without changing the
toplevel set.
2. Observe the "Launching …" OSD appear at the 2s mark.
3. Intermittently: the OSD does not clear after the window is up and does not
clear after 15s. It stays until omarchy-shell osd close /
omarchy restart shell.
Suggested fixes
- Have
Osd.show()enforce a hard maximum lifetime even forduration <= 0
(e.g. a separate long guard timer, ~30–60s), so no OSD can outlive its
purpose.
- Make the launch feedback converge on shell state rather than fire-and-forget:
issue show/close through a single serialized path (one IPC target method
that owns the launch-OSD lifecycle), or re-issue close on a short repeating
timer until osd state reports closed.
- In maybeFinishLaunchFeedback() / launchTimeout, always send close
unconditionally on timeout (drop the if (root.launchOsdOpen) guard for the
timeout path) and verify via the osd state IPC.
System details
- Omarchy: 4.0.1-1
- Kernel: 7.1.9-arch1-2
- Hyprland: 0.56.2 (v0.56.2, efb5099)
- Quickshell: 0.3.1 (Arch)
- CPU: Intel Core i3-2350M
- GPU: Intel 2nd Gen Core integrated graphics [8086:0116] (Sandy Bridge)
View raw text
# Launch OSD ("Launching <app>…") can get permanently stuck on screen
## What happened
After launching an app from the Omarchy menu, the persistent "Launching <app>…"
OSD overlay stayed on screen indefinitely. It never cleared on its own. The
`omarchy-osd` layer-shell surface remained up (visible card, `duration: 0`),
and `omarchy-shell osd close` was required to dismiss it manually.
## What was expected
The launch OSD should always clear — either when the launched window appears,
or via the 15s timeout fallback — with no path that leaves it stranded.
## Where it comes from
`shell/services/AppLibrary.qml`:
- `beginLaunchFeedback(name)` starts `launchDelay` (2000 ms) and
`launchTimeout` (15000 ms).
- `launchDelay.onTriggered` sets `launchOsdOpen = true` and calls
`Quickshell.execDetached(["omarchy-shell", "osd", "show",
{icon, message, duration: 0}])`.
- `closeLaunchFeedback()` calls
`Quickshell.execDetached(["omarchy-shell", "osd", "close"])`.
`shell/plugins/osd/Osd.qml`: `show()` with `duration <= 0` calls
`hideTimer.stop()` and leaves `opened = true` until an explicit `close()`.
There is no self-heal / no max lifetime for a `duration: 0` OSD.
## Root cause (two compounding issues)
1. **Show/close race.** `show` and `close` are dispatched as two *separate
detached* `omarchy-shell` processes. Each then makes its own `qs ipc call`
to the shell. Delivery order is decided by process scheduling, not by the
order the QML issued them. When a toplevel appears in roughly the same tick
the 2s `launchDelay` fires, `closeLaunchFeedback()` runs right after
`launchDelay.onTriggered`, so a `close` IPC can reach the shell *before* the
`show` IPC. Net result: `opened` ends up `true` with no timer, forever.
2. **Single-shot backstop with no retry.** For a launch that never produces a
detectable new/active toplevel (app already running and merely refocused so
`ToplevelManager.toplevels` count and `activeToplevel` are unchanged, a
background/agent app, or a failed launch), the only thing that clears the
OSD is the one-shot `launchTimeout` → single `closeLaunchFeedback()`. If
that lone detached `close` fails to land (shell busy past the 2s
`OMARCHY_SHELL_IPC_TIMEOUT`, transient error), nothing ever retries and the
OSD is stranded.
## Steps to reproduce
Exact timing is racy, but the shape:
1. From the Omarchy menu, launch an app whose window takes ~2s to map, or an
app that is already open and just gets refocused without changing the
toplevel set.
2. Observe the "Launching …" OSD appear at the 2s mark.
3. Intermittently: the OSD does not clear after the window is up and does not
clear after 15s. It stays until `omarchy-shell osd close` /
`omarchy restart shell`.
## Suggested fixes
- Have `Osd.show()` enforce a hard maximum lifetime even for `duration <= 0`
(e.g. a separate long guard timer, ~30–60s), so no OSD can outlive its
purpose.
- Make the launch feedback converge on shell state rather than fire-and-forget:
issue `show`/`close` through a single serialized path (one IPC target method
that owns the launch-OSD lifecycle), or re-issue `close` on a short repeating
timer until `osd state` reports `closed`.
- In `maybeFinishLaunchFeedback()` / `launchTimeout`, always send `close`
unconditionally on timeout (drop the `if (root.launchOsdOpen)` guard for the
timeout path) and verify via the `osd state` IPC.
## System details
- Omarchy: 4.0.1-1
- Kernel: 7.1.9-arch1-2
- Hyprland: 0.56.2 (v0.56.2, efb5099)
- Quickshell: 0.3.1 (Arch)
- CPU: Intel Core i3-2350M
- GPU: Intel 2nd Gen Core integrated graphics [8086:0116] (Sandy Bridge)
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.
No decision has been recorded yet.