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

omarchy-menu-keybindings (Super+K) hangs at 100% CPU when a Lua config calls ipairs(hl.get_*())

Reported by FernandoJVideira · filed Aug 30, 2026Closed upstream on 2026-09-20 by bjarneo via an automated message, as a duplicate of #7025 (R-1816). GitHub close reason: completed.

Received and recorded verbatim. Nothing has been checked yet.

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

omarchy-menu-keybindings (Super+K) hangs at 100% CPU when a Lua config calls ipairs(hl.get_*())revision 1 · captured 2026-10-04 12:03 UTC via poll

Environment
- Omarchy: 4.0.1-1
- Hyprland: 0.56.2
- Lua: 5.5.1

Summary

Pressing the keybindings-menu shortcut (Super+K → /usr/share/omarchy/bin/omarchy-menu-keybindings) can hang indefinitely and pin a CPU core at 100%, with the menu never appearing. Repeated presses stack up more hung lua processes, each burning ~10-15% CPU, until the whole system bogs down.

Root cause

To discover Lua-defined keybinds (hl.bind(...)), the script re-executes the user's hyprland.lua in a sandboxed Lua interpreter via build_lua_bind_cache():

lua <<'LUA'
...
hl = setmetatable({
  dsp = dsp_proxy("hl.dsp"),
  bind = function(...) ... end,
  get_config = function() return nil end,
}, {
  __index = function() return noop end,
})
...
local ok, err = pcall(dofile, config)
LUA

(omarchy-menu-keybindings, around lines 163–230, noop defined at line 182.)

noop is:

noop = setmetatable({}, {
  __index = function() return noop end,
  __call = function() return noop end,
})

Any hl.* method that isn't one of the two explicitly stubbed (bind, get_config) resolves through hl's __index to noop, and indexing into noop also always returns noop — for any key, including numeric ones.

Since Lua 5.3, ipairs(t) iterates using ordinary indexed access (t[1], t[2], …) and stops at the first nil — which respects __index. Because noop[i] never returns nil for any i, any config file that does `ipairs(hl.get_something())` for an unstubbed `hl` accessor loops forever when scanned by this sandbox, even though the same code is completely correct under real Hyprland (where hl.get_monitors() etc. return real arrays).

Minimal repro

-- ~/.config/hypr/monitors.lua (or any required file)
for _, mon in ipairs(hl.get_monitors()) do
  -- never reached in real Hyprland issue, but hangs the Super+K scanner
end

With that in hyprland.lua's require chain, every cache-miss run of omarchy-menu-keybindings spawns a lua process that spins at ~100% of one core forever (confirmed via /proc/<pid> — state R, not blocked on I/O). Because the scanner uses lua <<'LUA' ... LUA inside a while read ... done < <(...) process substitution, killing/timing out the outer bash omarchy-menu-keybindings script does not reliably reap the orphaned lua child — it can keep running and consuming CPU indefinitely after the parent script has "returned."

Suggested fix

Any of:
1. Give noop's metatable an explicit __index that returns nil for numeric/array-style keys (or otherwise makes ipairs(noop) terminate at index 1), while still returning a callable no-op for named method access.
2. Special-case common list-returning accessors (get_monitors, get_workspaces, get_windows, etc.) in the stub to return {} instead of falling through to noop.
3. Wrap the dofile(config) call with a hard timeout (e.g. alarm/os.clock() watchdog, or run it under the outer timeout and make sure the sandboxed lua process is actually killed — not just the parent) so a bug in a user config can't wedge the picker or leak orphaned processes.

Option 1 or 2 is the real fix (removes the footgun for any future hl.get_*/hl.list_* accessor a user might loop over); option 3 is good defense-in-depth regardless, since right now a hang here doesn't even get cleaned up by the surrounding timeout/process-substitution plumbing.

Impact

Any user config that iterates monitors, workspaces, or windows via the real hl API (a documented, encouraged pattern — see the in-repo comment in the default monitors.lua template recommending hl.get_monitor()/hl.get_monitors() over shelling out to hyprctl) will hit this the moment they press the keybindings-menu shortcut, with no error message — just a silent hang and climbing CPU.

View raw text
**Environment**
- Omarchy: 4.0.1-1
- Hyprland: 0.56.2
- Lua: 5.5.1

**Summary**

Pressing the keybindings-menu shortcut (Super+K → `/usr/share/omarchy/bin/omarchy-menu-keybindings`) can hang indefinitely and pin a CPU core at 100%, with the menu never appearing. Repeated presses stack up more hung `lua` processes, each burning ~10-15% CPU, until the whole system bogs down.

**Root cause**

To discover Lua-defined keybinds (`hl.bind(...)`), the script re-executes the user's `hyprland.lua` in a sandboxed Lua interpreter via `build_lua_bind_cache()`:

```bash
lua <<'LUA'
...
hl = setmetatable({
  dsp = dsp_proxy("hl.dsp"),
  bind = function(...) ... end,
  get_config = function() return nil end,
}, {
  __index = function() return noop end,
})
...
local ok, err = pcall(dofile, config)
LUA
```

(`omarchy-menu-keybindings`, around lines 163–230, `noop` defined at line 182.)

`noop` is:

```lua
noop = setmetatable({}, {
  __index = function() return noop end,
  __call = function() return noop end,
})
```

Any `hl.*` method that isn't one of the two explicitly stubbed (`bind`, `get_config`) resolves through `hl`'s `__index` to `noop`, and indexing *into* `noop` also always returns `noop` — for any key, including numeric ones.

Since Lua 5.3, `ipairs(t)` iterates using ordinary indexed access (`t[1]`, `t[2]`, …) and stops at the first `nil` — which respects `__index`. Because `noop[i]` never returns `nil` for any `i`, **any config file that does `ipairs(hl.get_something())` for an unstubbed `hl` accessor loops forever** when scanned by this sandbox, even though the same code is completely correct under real Hyprland (where `hl.get_monitors()` etc. return real arrays).

**Minimal repro**

```lua
-- ~/.config/hypr/monitors.lua (or any required file)
for _, mon in ipairs(hl.get_monitors()) do
  -- never reached in real Hyprland issue, but hangs the Super+K scanner
end
```

With that in `hyprland.lua`'s require chain, every cache-miss run of `omarchy-menu-keybindings` spawns a `lua` process that spins at ~100% of one core forever (confirmed via `/proc/<pid>` — state `R`, not blocked on I/O). Because the scanner uses `lua <<'LUA' ... LUA` inside a `while read ... done < <(...)` process substitution, killing/timing out the outer `bash omarchy-menu-keybindings` script does **not** reliably reap the orphaned `lua` child — it can keep running and consuming CPU indefinitely after the parent script has "returned."

**Suggested fix**

Any of:
1. Give `noop`'s metatable an explicit `__index` that returns `nil` for numeric/array-style keys (or otherwise makes `ipairs(noop)` terminate at index 1), while still returning a callable no-op for named method access.
2. Special-case common list-returning accessors (`get_monitors`, `get_workspaces`, `get_windows`, etc.) in the stub to return `{}` instead of falling through to `noop`.
3. Wrap the `dofile(config)` call with a hard timeout (e.g. `alarm`/`os.clock()` watchdog, or run it under the outer `timeout` and make sure the sandboxed `lua` process is actually killed — not just the parent) so a bug in a user config can't wedge the picker or leak orphaned processes.

Option 1 or 2 is the real fix (removes the footgun for *any* future `hl.get_*`/`hl.list_*` accessor a user might loop over); option 3 is good defense-in-depth regardless, since right now a hang here doesn't even get cleaned up by the surrounding `timeout`/process-substitution plumbing.

**Impact**

Any user config that iterates monitors, workspaces, or windows via the real `hl` API (a documented, encouraged pattern — see the in-repo comment in the default `monitors.lua` template recommending `hl.get_monitor()`/`hl.get_monitors()` over shelling out to `hyprctl`) will hit this the moment they press the keybindings-menu shortcut, with no error message — just a silent hang and climbing CPU.

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/12712· confirmed
  • Fixed bypr/7564· confirmed
  • Fixed bypr/11733· confirmed
  • Fixed bypr/12155· 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.

No decision has been recorded yet.