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.
A user's own binds in ~/.config/hypr/bindings.lua never reach the keybindings menu's Lua scan
Reported by linyiru · filed Sep 29, 2026Closed upstream on 2026-09-29 by its own reporter linyiru, as a duplicate of #13276. GitHub close reason: duplicate.
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
What happens
omarchy-menu-keybindings keeps its own Lua scan of the user's config, because Hyprland reports every Lua bind as dispatcher __lua with no arg and no id. The scan dies partway through the shipped config, and the pcall wrapped around it discards the error, so nothing surfaces.
The result is that no bind in `~/.config/hypr/bindings.lua` is ever scanned. A user's own binds get no dispatcher recovery and no id in the keybindings menu.
Why
Two things line up:
default/hypr/omarchy.lua:18isrequire("default.hypr.qconsole"). Under the scan'snoopstub,hl.get_workspacereturns a stub table, so indefault/hypr/qconsole.lua:123themon.scale > 0test compares a table with a number and throws.config/hypr/hyprland.luarequiresdefault.hypr.omarchyat line 14 andhypr.bindingsat line 21. The throw at step 1 unwinds past line 21.
The 229 default binds registered before line 18 do survive, which is why the menu looks fine on a stock install and why this has gone unnoticed.
Reproduce
# extract the scan from the menu, verbatim
sed -n "/^ lua <<'LUA'\$/,/^LUA\$/p" bin/omarchy-menu-keybindings | sed '1d;$d' >/tmp/scan.lua
# a stock config plus one personal bind, where the comments in the shipped
# bindings.lua tell you to put it
mkdir -p /tmp/h/.config && cp -r config/hypr /tmp/h/.config/hypr
echo 'o.bind("SUPER + ALT + P", "My own bind", "true")' >>/tmp/h/.config/hypr/bindings.lua
env -i PATH="$PATH" HOME=/tmp/h OMARCHY_PATH="$PWD" DEBUG=1 lua /tmp/scan.lua[DEBUG] lua bind scan failed: .../default/hypr/qconsole.lua:123: attempt to compare number with table229 lines on stdout, and My own bind is not one of them. Move require("default.hypr.helpers") and require("hypr.bindings") ahead of require("default.hypr.omarchy") in hyprland.lua and it becomes 230 lines with My own bind present, which confirms the load order is what is doing it.
Why it matters beyond the id
A user who renames or translates a bind description in ~/.config/hypr/bindings.lua gets no id for it, so the menu falls back to the description. Since the menu's row pairing and its sort order both key on a name, a renamed bind loses the row it shared and the tier it belonged to. Measured on linyiru:menu-sort-by-bind-id (the follow-up to #12557, which moves those two decisions onto the id), with two chords declaring the same id = "Close window" and a translated description:
# both binds in ~/.config/hypr/bindings.lua, shipped load order
SUPER ALT + Q → 關閉視窗
SUPER ALT + W → 關閉視窗
# same two binds, load order changed so the scan reaches them
SUPER ALT + W / SUPER ALT + Q → 關閉視窗So this gates the bind-id work: the ids are threaded correctly, and a personal config still cannot use them, because the scan never reads the file they are declared in.
Scope
Pre-existing and unrelated to bind ids. I am reporting rather than patching because the fix is a judgement call I would rather not make alone: either the scan's stub grows enough shape for qconsole.lua to survive it, or the scan stops depending on the shipped config loading cleanly, or pcall stops being allowed to fail silently. Happy to implement whichever you prefer.
Verified 2026-09-29 on Arch, Lua 5.5.1, at 647b4a21.
🤖 Generated with Claude Code
View raw text
### What happens
`omarchy-menu-keybindings` keeps its own Lua scan of the user's config, because Hyprland reports every Lua bind as dispatcher `__lua` with no arg and no id. The scan dies partway through the *shipped* config, and the `pcall` wrapped around it discards the error, so nothing surfaces.
The result is that **no bind in `~/.config/hypr/bindings.lua` is ever scanned**. A user's own binds get no dispatcher recovery and no id in the keybindings menu.
### Why
Two things line up:
1. `default/hypr/omarchy.lua:18` is `require("default.hypr.qconsole")`. Under the scan's `noop` stub, `hl.get_workspace` returns a stub table, so in `default/hypr/qconsole.lua:123` the `mon.scale > 0` test compares a table with a number and throws.
2. `config/hypr/hyprland.lua` requires `default.hypr.omarchy` at line 14 and `hypr.bindings` at line 21. The throw at step 1 unwinds past line 21.
The 229 default binds registered before line 18 do survive, which is why the menu looks fine on a stock install and why this has gone unnoticed.
### Reproduce
```bash
# extract the scan from the menu, verbatim
sed -n "/^ lua <<'LUA'\$/,/^LUA\$/p" bin/omarchy-menu-keybindings | sed '1d;$d' >/tmp/scan.lua
# a stock config plus one personal bind, where the comments in the shipped
# bindings.lua tell you to put it
mkdir -p /tmp/h/.config && cp -r config/hypr /tmp/h/.config/hypr
echo 'o.bind("SUPER + ALT + P", "My own bind", "true")' >>/tmp/h/.config/hypr/bindings.lua
env -i PATH="$PATH" HOME=/tmp/h OMARCHY_PATH="$PWD" DEBUG=1 lua /tmp/scan.lua
```
```
[DEBUG] lua bind scan failed: .../default/hypr/qconsole.lua:123: attempt to compare number with table
```
229 lines on stdout, and `My own bind` is not one of them. Move `require("default.hypr.helpers")` and `require("hypr.bindings")` ahead of `require("default.hypr.omarchy")` in `hyprland.lua` and it becomes 230 lines with `My own bind` present, which confirms the load order is what is doing it.
### Why it matters beyond the id
A user who renames or translates a bind description in `~/.config/hypr/bindings.lua` gets no id for it, so the menu falls back to the description. Since the menu's row pairing and its sort order both key on a name, a renamed bind loses the row it shared and the tier it belonged to. Measured on `linyiru:menu-sort-by-bind-id` (the follow-up to #12557, which moves those two decisions onto the id), with two chords declaring the same `id = "Close window"` and a translated description:
```
# both binds in ~/.config/hypr/bindings.lua, shipped load order
SUPER ALT + Q → 關閉視窗
SUPER ALT + W → 關閉視窗
# same two binds, load order changed so the scan reaches them
SUPER ALT + W / SUPER ALT + Q → 關閉視窗
```
So this gates the bind-id work: the ids are threaded correctly, and a personal config still cannot use them, because the scan never reads the file they are declared in.
### Scope
Pre-existing and unrelated to bind ids. I am reporting rather than patching because the fix is a judgement call I would rather not make alone: either the scan's stub grows enough shape for `qconsole.lua` to survive it, or the scan stops depending on the shipped config loading cleanly, or `pcall` stops being allowed to fail silently. Happy to implement whichever you prefer.
Verified 2026-09-29 on Arch, Lua 5.5.1, at `647b4a21`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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/11851· confirmed
- Fixed bypr/7564· confirmed
- Fixed bypr/13276· confirmed
- Fixed bypr/11733· confirmed
- Related PRpr/12557· 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.