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.
Menu rebuilds its list in a tight loop (flicker, selection jumps to bottom) when user desktop files override system desktop ids
Reported by uMaj35ty · filed Aug 16, 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?
- 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. So far: partly in the audited area (menu) — decided by agents · verified; matches no known issue — decided by agents · verified. The outcome (valid, duplicate, not a bug, …) is not decided yet. See the decision.
Original report
Menu rebuilds its list in a tight loop (flicker, selection jumps to bottom) when user desktop files override system desktop ids
Summary
On Omarchy 4.0.0 (Quattro), the menu rebuilds its app list in a tight loop —
measured every ~10ms — whenever ~/.local/share/applications/ contains.desktop files that **override same-id entries from/usr/share/applications/** (the standard XDG override pattern: Steam game
launchers, un-hiding NoDisplay=true entries, etc.).
While the menu is open this produces:
- constant list flicker (rows re-instantiated several times per second)
- the keyboard/hover selection being dragged away, often ending on the first
or last row
- it happens with no input at all (no mouse movement, no typing)
- hidden-entries.sh respawning continuously (a bash process every ~300ms,
pinning a core)
- every list driven by the shared AppLibrary is affected; the apps list
(longest) is the most visible. Lists not backed by DesktopEntries
(e.g. emojis) are unaffected.
Environment
- Omarchy 4.0.0-1 (upgraded from 3.x to Quattro on 2026-08-16)
- Hyprland (Quattro lua config), Quickshell shell
- NVIDIA RTX 5060 Ti, two monitors (DP-3 2560x1440@180 scale 1, HDMI-A-1
1920x1080@60 scale 1) — the bug is resolution/scale/GPU independent as far
as I can tell; I reproduced it at 60Hz, scale 1 on both, etc.
- 88 files in ~/.local/share/applications/, of which ~80 share ids with
/usr/share/applications/ (Steam games + un-hidden system entries)
Reproduce
- Install Quattro.
- Drop a few override entries into the user dir, e.g.:
mkdir -p ~/.local/share/applications
for f in imv mpv btop; do
sed 's/^NoDisplay=true/NoDisplay=false/' \
/usr/share/applications/$f.desktop > ~/.local/share/applications/$f.desktop
done (On my machine the count matters: with all ~80 overrides the menu
rebuilds ~58 times in 10s; with ~40 it's ~20; with 0 it's 2 — i.e. the
loop frequency scales with the number of overridden ids.)
- Restart the shell, open the menu (
Super+Space), enter the Apps list,
and leave it alone. Watch it flicker and the selection jump.
ps shows hidden-entries.sh Hyprland:Hyprland:omarchy respawning.
Root cause analysis (instrumented)
I cloned the omarchy.menu plugin and added counters. With the menu open
and zero input over 10 seconds:
| metric | with overrides | without overrides |
|---|---|---|
rebuildDisplay() calls | 40–58 | 2 |
selectFromPointer() calls (hover events) | 765 | 0 |
selectedIndex changes (self-oscillating 0↔1↔2) | every ~300ms | 0 |
No file in ~/.local/share or ~/.config changes during the loop (checked
mtime and atime) — so this is not the "login shell touches ~/.local/share"
variant already defended against in AppLibrary.qml (the mise comment abovehiddenEntryScan). The chain is:
DesktopEntries.applications(Quickshell) re-emitsvaluesChanged
repeatedly on its own while same-id entries exist in both the user and
system dirs. Nothing writes to disk between emissions — the churn is
internal to the model (duplicate-id resolution order instability is my
best guess).
2. AppLibrary.qml's `Connections { target: DesktopEntries.applications;
onValuesChanged: hiddenEntryScan.running = true; ... }` reruns the scan
and re-emits appsChanged.
3. Menu.qml's `Connections { target: root.appLibrary; onAppsChanged() {
if (root.providersLoaded["apps"]) root.mergeAppRows() } }` re-merges rows
and calls rebuildDisplay() — measured at up to one rebuild per
event-loop turn.
4. Every rebuild re-instantiates delegates, which fires onEntered/
onPositionChanged for the row under the (stationary) pointer. The
PointerMoveGate filters the first sample, but subsequent delegate
swaps move the local coordinate by a full row height, so ~half the hover
events pass moved() and rewrite selectedIndex; revealCursor() then
scrolls, which sweeps more delegates under the pointer and keeps the
churn alive. That is the visible "flicker + selection dragged to the
bottom/first row".
Workaround I'm running (menu plugin clone)
Two changes in a cloned omarchy.menu plugin stop it completely (58 → 4
rebuilds per 10s with all files restored, selection stable, hover still
works):
// Menu.qml — pause list rebuilds while the menu is open; the open path
// force-reloads instead:
Connections {
target: root.appLibrary
function onAppsChanged() {
if (root.opened) return
if (root.providersLoaded["apps"]) root.mergeAppRows()
}
}
// in the open path, next to invalidateVolatileProvider(activeMenu):
root.providersLoaded[activeMenu] = false // force fresh pull on openPlus a cooldown in a local PointerMoveGate copy (disarm() sets a 250ms
window in which moved() returns false, also called fromListView.onContentYChanged) so hover churn from rebuilds/scrolling can no
longer steal the selection.
Suggested fix upstream
Any of these would help; the first is the cheapest and matches what I
validated:
- In
Menu.qml, don't rebuild fromappsChangedwhile the menu is open;
refresh on open instead (appsChanged while closed still merges, and
installing an app writes a desktop file, so the closed-state signal
fires and data stays fresh).
2. Debounce hiddenEntryScan / coalesce valuesChanged in AppLibrary.qml
(a scan restart per emission with no dedup window amplifies the churn).
3. The deeper question: why DesktopEntries.applications re-emits
valuesChanged with no filesystem change when duplicate ids are present
— that may belong upstream in Quickshell, but Omarchy can shield itself
with (1)/(2).
Happy to provide the instrumented logs or test a fix.
View raw text
# Menu rebuilds its list in a tight loop (flicker, selection jumps to bottom) when user desktop files override system desktop ids
## Summary
On Omarchy 4.0.0 (Quattro), the menu rebuilds its app list in a tight loop —
measured **every ~10ms** — whenever `~/.local/share/applications/` contains
`.desktop` files that **override same-id entries from
`/usr/share/applications/`** (the standard XDG override pattern: Steam game
launchers, un-hiding `NoDisplay=true` entries, etc.).
While the menu is open this produces:
- constant list flicker (rows re-instantiated several times per second)
- the keyboard/hover selection being dragged away, often ending on the first
or last row
- it happens **with no input at all** (no mouse movement, no typing)
- `hidden-entries.sh` respawning continuously (a bash process every ~300ms,
pinning a core)
- every list driven by the shared `AppLibrary` is affected; the apps list
(longest) is the most visible. Lists not backed by `DesktopEntries`
(e.g. emojis) are unaffected.
## Environment
- Omarchy 4.0.0-1 (upgraded from 3.x to Quattro on 2026-08-16)
- Hyprland (Quattro lua config), Quickshell shell
- NVIDIA RTX 5060 Ti, two monitors (DP-3 2560x1440@180 scale 1, HDMI-A-1
1920x1080@60 scale 1) — the bug is resolution/scale/GPU independent as far
as I can tell; I reproduced it at 60Hz, scale 1 on both, etc.
- 88 files in `~/.local/share/applications/`, of which ~80 share ids with
`/usr/share/applications/` (Steam games + un-hidden system entries)
## Reproduce
1. Install Quattro.
2. Drop a few override entries into the user dir, e.g.:
```bash
mkdir -p ~/.local/share/applications
for f in imv mpv btop; do
sed 's/^NoDisplay=true/NoDisplay=false/' \
/usr/share/applications/$f.desktop > ~/.local/share/applications/$f.desktop
done
```
(On my machine the count matters: with all ~80 overrides the menu
rebuilds ~58 times in 10s; with ~40 it's ~20; with 0 it's 2 — i.e. the
loop frequency scales with the number of overridden ids.)
3. Restart the shell, open the menu (`Super+Space`), enter the Apps list,
and leave it alone. Watch it flicker and the selection jump.
`ps` shows `hidden-entries.sh Hyprland:Hyprland:omarchy` respawning.
## Root cause analysis (instrumented)
I cloned the `omarchy.menu` plugin and added counters. With the menu open
and **zero input** over 10 seconds:
| metric | with overrides | without overrides |
| --- | --- | --- |
| `rebuildDisplay()` calls | 40–58 | 2 |
| `selectFromPointer()` calls (hover events) | 765 | 0 |
| `selectedIndex` changes (self-oscillating 0↔1↔2) | every ~300ms | 0 |
No file in `~/.local/share` or `~/.config` changes during the loop (checked
mtime and atime) — so this is not the "login shell touches ~/.local/share"
variant already defended against in `AppLibrary.qml` (the mise comment above
`hiddenEntryScan`). The chain is:
1. `DesktopEntries.applications` (Quickshell) re-emits `valuesChanged`
repeatedly on its own while same-id entries exist in both the user and
system dirs. Nothing writes to disk between emissions — the churn is
internal to the model (duplicate-id resolution order instability is my
best guess).
2. `AppLibrary.qml`'s `Connections { target: DesktopEntries.applications;
onValuesChanged: hiddenEntryScan.running = true; ... }` reruns the scan
and re-emits `appsChanged`.
3. `Menu.qml`'s `Connections { target: root.appLibrary; onAppsChanged() {
if (root.providersLoaded["apps"]) root.mergeAppRows() } }` re-merges rows
and calls `rebuildDisplay()` — measured at up to one rebuild per
event-loop turn.
4. Every rebuild re-instantiates delegates, which fires `onEntered`/
`onPositionChanged` for the row under the (stationary) pointer. The
`PointerMoveGate` filters the first sample, but subsequent delegate
swaps move the local coordinate by a full row height, so ~half the hover
events pass `moved()` and rewrite `selectedIndex`; `revealCursor()` then
scrolls, which sweeps more delegates under the pointer and keeps the
churn alive. That is the visible "flicker + selection dragged to the
bottom/first row".
## Workaround I'm running (menu plugin clone)
Two changes in a cloned `omarchy.menu` plugin stop it completely (58 → 4
rebuilds per 10s with all files restored, selection stable, hover still
works):
```qml
// Menu.qml — pause list rebuilds while the menu is open; the open path
// force-reloads instead:
Connections {
target: root.appLibrary
function onAppsChanged() {
if (root.opened) return
if (root.providersLoaded["apps"]) root.mergeAppRows()
}
}
// in the open path, next to invalidateVolatileProvider(activeMenu):
root.providersLoaded[activeMenu] = false // force fresh pull on open
```
Plus a cooldown in a local `PointerMoveGate` copy (`disarm()` sets a 250ms
window in which `moved()` returns false, also called from
`ListView.onContentYChanged`) so hover churn from rebuilds/scrolling can no
longer steal the selection.
## Suggested fix upstream
Any of these would help; the first is the cheapest and matches what I
validated:
1. In `Menu.qml`, don't rebuild from `appsChanged` while the menu is open;
refresh on open instead (appsChanged while closed still merges, and
installing an app writes a desktop file, so the closed-state signal
fires and data stays fresh).
2. Debounce `hiddenEntryScan` / coalesce `valuesChanged` in `AppLibrary.qml`
(a scan restart per emission with no dedup window amplifies the churn).
3. The deeper question: why `DesktopEntries.applications` re-emits
`valuesChanged` with no filesystem change when duplicate ids are present
— that may belong upstream in Quickshell, but Omarchy can shield itself
with (1)/(2).
Happy to provide the instrumented logs or test a fix.
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
- Related PRpr/7413· confirmed
- Related PRpr/14992· 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.
- Decided by agents · verified: partly in the audited area (menu)
- Decided by agents · verified: matches no known issue
The outcome (valid, duplicate, not a bug, …) is not decided yet.