Repository navigation
omarchy-menu-keybindings hangs forever when a user Lua config iterates hl.get_monitors() #7025
Description
What happened
omarchy-menu-keybindings (SUPER + K) never opens. The process hangs indefinitely — no menu, no error, no timeout. omarchy-menu-keybindings --print also hangs.
The hang is triggered by any user Lua config that iterates one of Hyprland's list getters (hl.get_monitors(), hl.get_windows(), hl.get_workspaces()) with ipairs at config-evaluation time.
What I expected
The keybindings menu opens and lists the bindings, or fails loudly.
Steps to reproduce
On Omarchy 4.0.0 (quattro), add a single line to ~/.config/hypr/monitors.lua:
for _, monitor in ipairs(hl.get_monitors()) do endThen run:
omarchy-menu-keybindings --print # hangs foreverHyprland itself is fine — hyprctl reload and hyprctl configerrors are clean, and the config behaves correctly at runtime. Only the keybindings scanner hangs.
Root cause
build_lua_bind_cache() in omarchy-menu-keybindings re-evaluates ~/.config/hypr/hyprland.lua in a bare lua interpreter, with hl replaced by a catch-all stub (bin/omarchy-menu-keybindings, around lines 181-232):
local noop
noop = setmetatable({}, {
__index = function() return noop end,
__call = function() return noop end,
})Because __index answers every key with a truthy value, hl.get_monitors() returns noop, and ipairs(noop) reads index 1, 2, 3, … without ever hitting nil. Infinite loop inside the lua subprocess. The while read loop feeding off it in bash blocks forever, so the menu never renders.
The stub already special-cases one getter to return a real value:
get_config = function()
return nil
end,The list getters just were not covered.
Suggested fix
Stub the list getters with real empty tables, so iteration terminates:
get_monitors = function() return {} end,
get_windows = function() return {} end,
get_workspaces = function() return {} end,A timeout around the lua subprocess would also turn this class of failure into a degraded menu instead of a silent hang.
Why this probably was not caught
No config under /usr/share/omarchy/default/hypr/ calls any hl.get_* getter at load time (grep -rn "get_monitors\|get_windows\|get_workspaces" /usr/share/omarchy/default/hypr/ returns nothing), so the stock configs never hit it.
Third-party Lua modules do. Unmodified upstream shezdy/hyprsplit init.lua iterates all three getters, so any user requiring it from their Hypr config gets the same hang.
My own trigger was dynamic monitor placement — computing the laptop's position from the live external monitor geometry, rather than hardcoding auto-right.
Workaround
Guard in ~/.config/hypr/hyprland.lua, right after the bootstrap dofile, detecting the stub (raw slot empty but __index conjures a value) and handing back real empty lists:
local probe = hl.get_monitors()
if type(probe) == "table" and rawget(probe, 1) == nil and probe[1] ~= nil then
hl.get_monitors = function() return {} end
hl.get_windows = function() return {} end
hl.get_workspaces = function() return {} end
endWith that in place omarchy-menu-keybindings --print exits 0 and lists all 231 bindings.
System details
- Omarchy 4.0.0-1 (stable channel)
- Hyprland 0.56.2-1
- Lua 5.5.1-1
- CPU: AMD Ryzen AI 7 PRO 350 w/ Radeon 860M
- GPU: AMD Radeon 840M / 860M Graphics (Krackan)
Activity
SamuelYancy commentedon Aug 15, 2026
Same root cause, confirmed independently on different hardware — but this can escalate well past a hang. On my machine it took the whole system down.
The difference is what sits inside the user's loop. The repro here uses an empty body:
which spins CPU but allocates nothing. Real configs put something in the body. Mine appends to a table:
Because
ipairsover thenoopstub never terminates, thattable.insertruns forever and each hungluagrows without bound.Amplification: since the menu never appears, you press SUPER + K again. Every press leaks another unbounded
lua. I accumulated 63 of them.Result (Omarchy 4.0.0-1, 64 GB RAM / 60 GiB usable):
luaprocesses holding ~56 GB of the ~57.5 GB total RSS; largest single process 6.6 GBFATAL:content/browser/gpu/gpu_data_manager_impl_private.cc:417] GPU process isn't usable. Goodbye.— recovered verbatim from its core dump — because its GPU process kept being OOM-killed and it hit the GPU-failure limitFrom the kernel OOM report:
The OOM task table shows the signature clearly — a repeating triplet of two
omarchy-menu-ke…shells followed by a multi-GBlua, 63 times over.Mechanism confirmed separately on Lua 5.5.1:
ipairsover the stub ran 5,000,001 iterations without hittingnil, andrawlen(stub)is0. Neutralising just that one loop in my config makes the scan complete cleanly and emit all 226 bindings, which isolates it to the same line you identified.Given that, I'd treat the suggested timeout around the
luasubprocess as required rather than nice-to-have, and pair it with a memory cap (ulimit -vin the subshell). A timeout alone still lets a config that allocates inside the loop exhaust a 64 GB machine — mine got there in roughly two minutes, well inside any generous timeout. The proposed empty-table stubs for the list getters fix the trigger; the cap contains the whole class.Worth noting for anyone hitting this:
omarchy-launch-browserruns the browser with--property=StandardError=null, so Chromium's GPU-failure output is discarded and nothing lands in the journal to explain the browser dying.System details, different from the original report: AMD Ryzen 5 9600X, AMD Radeon RX 7900 XT (Navi 31) desktop + Raphael iGPU, Omarchy 4.0.0-1, Hyprland 0.56.2-1, Lua 5.5.1-1, Chromium 151.0.7922.137-1, Linux 7.1.8-arch1-3.
Filed by Claude Opus 5 via Claude Code.
mmsami commentedon Aug 27, 2026
Reproduced independently on 4.0.1-1, confirming this isn't specific to
hl.get_monitors()inmonitors.lua.My trigger: a personal
~/.config/hypr/monitor-fit.lua(required fromhyprland.lua) that prunes stale window records at top-level load time (not inside anhl.on(...)handler):Same symptom:
omarchy menu keybindings/--printhangs forever,hyprctl reloadandhyprctl configerrorsstay clean, real runtime behavior is unaffected — only the static scanner hangs. Confirms thenoopstub's__index/__callpattern is the general problem for any unimplementedhl.*getter, not just the ones already listed.Local workaround for anyone hitting this before it's fixed upstream: detect the placeholder by checking for a
__callmetamethod (real getters return plain tables, the stub'snoopis also callable) and skip the loop when it's present:omarchybot commentedon Aug 29, 2026
Confirmed, and your root-cause analysis holds exactly as written. Reviewed on a disposable Omarchy VM, never on a real desktop. Two open PRs fix it, and there is one thing about reproducing it today that is worth recording here.
The reproduction in this issue no longer hangs on current
quattro, and that is masking rather than a fix.default/hypr/qconsole.lualanded on 18 August in 8398771, three days after you filed. It callsfit()at load time on line 79, andfit()reachesqconsole.lua:64,if not monitor or not monitor.scale or monitor.scale <= 0 then. Under the scan's stub,hl.get_active_monitor()and.scaleboth answer with the truthynooptable, so Lua raisesattempt to compare table with number. That happens on the path fromconfig/hypr/hyprland.lua:14→default/hypr/omarchy.lua:18, five lines beforehyprland.lua:19loads~/.config/hypr/monitors.lua, andpcall(dofile, config)atbin/omarchy-menu-keybindings:234swallows the error. So the scan now exits in about a second instead of spinning, and anyone retrying your steps gets a clean-looking result. Put the same loop ahead ofhyprland.lua:14and it hangs exactly as you described. The defect is untouched; only the stock load order changed.That abort is itself a bug, and it costs users their bindings today. Measured on the worker with an ordinary
o.bind(...)in~/.config/hypr/bindings.lua(required athyprland.lua:21, past the abort): the scan recovers 226 binds and none of them is the user's. Personal Lua-registered bindings are silently missing from the keybindings menu on every stock install, with or without a hanging loop in the config.On @mmsami's generalisation: correct — any un-stubbed
hl.*getter behaves this way, not just the list getters, because thehltable's own catch-all__indexatbin/omarchy-menu-keybindings:224answers every un-stubbed member with the sentinel, and the sentinel's__indexat:189then answers every key with itself. One narrowing:pairsdoes not share it. The sentinel has no__pairsmetamethod, sopairsfalls back tonextover its raw table, which is empty — 0 iterations, confirmed on Lua 5.5.1. It isipairs, and hand-rolled numeric orwhile-truthiness loops, that never terminate.On @SamuelYancy's point that a timeout alone is not enough: the two PRs split on exactly that.
__indexreturnnilfor numeric keys, and nothing else. Verified by mutation on the worker: with a loop placed ahead of the qconsole abort, the scan runs until it is killed with the change reverted and completes in under a second with it. This is the only metatable mechanism that works —__lenreturning 0 does not stopipairs, and__ipairsis ignored from Lua 5.4 on. It does not fix the qconsole abort and adds no timeout.nilchange, plus__len,__lt/__le, arithmetic,__concatand__tostringso load-time reads degrade instead of raising, plustimeout 10around the whole scan. Run against a stock config on the worker it produces no abort and recovers 227 binds, the user's own among them. Thetimeoutis the part that bounds thewhile hl.some_api() doshape neither metatable change closes.Neither carries a memory cap, so the escalation you documented is bounded by the timeout in #7564 rather than prevented. Which PR to take, or whether to take both, is the maintainer's decision and not one I will make here.
#8819, filed on 28 August, is the same defect: same file, same
build_lua_bind_cache(), same sentinel, same non-terminatingipairs. This issue is the earlier report and would normally be the one kept; consolidating the two is the maintainer's call.Reviewed by Claude Opus 5 with an independent second opinion from Codex at xhigh reasoning, which independently confirmed the duplicate call against both issue texts and the
quattrosource, verified the load order above, and contributed thepairsnarrowing.mikebenner commentedon Aug 29, 2026
Confirmed on packaged Omarchy 4.0.1-1 (Hyprland 0.56, Lua 5.5). Super+K never opens the menu;
omarchy-menu-keybindings --printhangs until killed.Trigger is load-time
ipairs(hl.get_monitors())in~/.config/hypr/monitors.lua. This package does not shipdefault/hypr/qconsole.lua, so the scan actually reaches that loop instead of aborting earlier onmonitor.scale <= 0. Each Super+K left a 100% CPUluachild reparented tosystemd --user.hyprctl reload/hyprctl configerrorsstayed clean.#7564 is the fix that covers this (numeric-nil on the stub so
ipairsterminates, plus the timeout so a leftover scan cannot outlive the menu).omarchybot commentedon Aug 30, 2026
@mikebenner's packaging detail is correct and it corrects something in my previous comment that matters for urgency. I said the reproduction here is masked on current
quattroby theqconsole.luaabort. That is true of the git branch and not of what users are running: the packagedomarchy 4.0.1-1ships nodefault/hypr/qconsole.luaat all, so there is nothing to abort the scan early and the loop is reached. Checked on a disposable VM running the stock 4.0.1-1 package —/usr/share/omarchy/default/hypr/has noqconsole.lua, andomarchy-menu-keybindings --printunder a~/.config/hypr/monitors.luacontaining the reported loop runs until it is killed, while the same command on the stock config returns in well under a second. Reproduced identically for @mmsami'sipairs(hl.get_windows())shape. The defect is live for everyone on 4.0.1-1 today, not only for people who put the loop ahead of the defaults.On the escalation to a system-wide lockup. The mechanism is real and I can name it: it is unbounded memory growth inside each hung
lua, and it is neither a fork bomb nor the compositor blocking on IPC. Nothing self-replicates — one keypress produces exactly one scanner, and I measured the process table staying flat at threeluaafter three presses rather than growing on its own. There is no IPC in the loop either:hl.get_monitorsresolves locally through the catch-all__indexatbin/omarchy-menu-keybindings:224to the sentinel, whose__callat:192returns itself, so the child Lua never talks to Hyprland, which is whyhyprctl reloadstays responsive throughout. What amplifies it is the cache: the hang happens insiderefresh_keybindings_cachebefore themvat:548, and there is no lock, so every press starts another full scan and nothing reaps the previous one. The per-press process signature is twoomarchy-menu-keshells and oneluaat ~100% CPU, which I reproduced exactly.Severity depends entirely on what is inside the loop, and this is worth being precise about. Measured on 4.0.1-1 with Lua 5.5.1, each capped so nothing could actually exhaust the machine:
do end)live[w.address] = truetable.insert(t, m)So an appending body grows at roughly 165 MB/s and does not stop. That establishes the mechanism by which a config could exhaust a large machine. But none of the three configs reported in this thread is that shape: the original repro and @mikebenner's have empty bodies, and @mmsami's writes
live[w.address] = true, wherew.addressis the same sentinel table every iteration, so it rewrites one key and does not grow. I cannot verify a particular incident on someone else's machine from here, and I am not treating the specific process counts and totals as established. What I will say is the part I proved: the hang is confirmed on the shipping package for every trigger reported here, and memory exhaustion is a demonstrated consequence of the same defect for the ordinary case of a loop body that appends.The two open PRs, since both claim this area. #7564's production change is a strict superset of #8876's — byte-for-byte the same numeric-
nil__index, plus__len,__lt/__le, arithmetic,__concatand__tostring, plustimeout 10around the scan. Both fix every trigger actually reported here; #8876's production change is a real fix and not merely something that makes its own test pass, and itstimeout 5is test-only. They separate on loop shapes that never index numerically, run against the packaged defaults:for _, m in ipairs(hl.get_monitors())for _, w in ipairs(hl.get_windows())while hl.get_monitors() do ... endwhile cur do cur = cur.next endNeither carries a memory cap, so neither prevents the exhaustion above — a timeout bounds how long each process has to allocate, not how fast, and at the rate measured a single scanner passes 1.5 GB in under two seconds, well inside ten.
One thing to weigh against #7564's timeout, which I had not considered until the second reviewer raised it. When the timeout fires, the incomplete result is still cached. The success test at
:526is[[ -n $dynamic ]], which turns only onhyprctl bindsand not on whether the Lua scan finished, so the temporary file is promoted by themv. Verified against #7564 on the worker with a stubbedhyprctl: the scan is killed at ten seconds, no diagnostic is printed even underDEBUG=1becausepcallcannot report an error when the process is signalled externally, a personalhl.binddeclared after the loop is missing from the output, and every later press serves that incomplete record set instantly from cache. The hang stops, which is the point — but it becomes a silent, sticky wrong answer rather than a visible failure. That is an argument for pairing the timeout with a diagnostic and with declining to cache a scan that did not finish, not an argument against the timeout.Which PR to take, whether to take the narrow change plus a separate hardening pass, and whether to consolidate #8819 into this issue, are all the maintainer's calls and I am not making them here.
Reviewed by Claude Opus 5 in Claude Code, with an independent second opinion from Codex at xhigh reasoning. Codex reached the same mechanism separately — unbounded memory rather than a fork bomb or blocked IPC, including the point that the bare loop does not itself grow — and contributed the caching finding above, which I had not reasoned about and then confirmed on the worker. Where it and I merely agree, its independence is not guaranteed, since it can read this session's own working notes.
settingzbot commentedon Sep 3, 2026
Same defect, hit on a real desktop — adding three things the thread doesn't have yet: a thermal escalation path that a memory cap would not contain, evidence that the "no longer reproduces" masking does not apply to the shipped release, and a trigger that involves no hand-edited config at all.
1. The escalation is not always OOM. Mine was thermal.
@SamuelYancy's runaway allocates inside the loop, so it ends in OOM. Mine allocates nothing:
pis the sentinel,p.nameis the sentinel, the comparison against a string is false, and the loop body never allocates. RSS stayed flat. Instead:luaprocesses, one perSUPER + Kpress, 15 of 20 cores at ~99%, load average 15.4package_throttle_count = 193372,core_throttle_count = 22822i5-13600KF, Omarchy 4.0.2-1, Hyprland 0.56.2-1, Lua 5.5.1-1, kernel 7.1.9-arch1-2.
Why this matters for the fix choice: the
ulimit -vcap suggested above would not have caught this at all — there is nothing to cap. A non-allocating body is the more likely shape in the wild, since the common pattern is scanning a list for a match and returning. Of the two open PRs, only #7564'stimeout 10bounds it. I confirmed the placement works:timeoutislua's direct parent there, Lua installs noSIGTERMhandler, and a spinning scan dies on the timeout leaving no stray process (exit=124,pgrep -x lua→ 0). Worth noting that a timeout bounds each press but not the count of presses — 15 presses is still 15 × 10s of full-core spin, which is why aflockis worth having alongside it (cf. #9057).2. The qconsole masking is
quattro-only. The release is fully exposed.The reviews on this issue and #8819 report that these reproductions no longer hang on current
quattro, becausedefault/hypr/qconsole.luaaborts the scan athyprland.lua:14before user files load. On the released 4.0.2-1 that users are actually running, that file does not exist:There is no abort. I instrumented every line of
~/.config/hypr/hyprland.luawith a sentinel write and the scan executes all 59 lines withDEBUG=1reporting no error. Running the real binary against a faithful copy of my config (cold cache,timeout -k 2 14):exit=0, 0sipairs(hl.get_monitors())inserted before line 14exit=124(hangs)ipairs(hl.get_loaded_plugins())appended after line 14, at line 60exit=124(hangs)So the position-dependence is an artifact of the development branch. On the release, the reproduction in this issue hangs exactly as originally filed, in either position. I'd suggest not letting "cannot reproduce on
quattro" lower the priority here.One methodology note for anyone re-testing: the records cache at
${XDG_CACHE_HOME:-$HOME/.cache}/omarchy/keybindings-<key>.recordsis keyed onhyprctl bindsand the active keymap, not on config contents, so a warm cache skips the Lua scan entirely and a modified config looks fine. My first attempt produced three confident false passes that way. Clear the cache, or pointXDG_CACHE_HOMEsomewhere empty, before concluding anything.3. Reachable without ever hand-editing a config.
Every reproduction so far is a loop the user wrote. Mine was generated:
A third-party plugin wrote
~/.config/hypr/border-fx.luaand addedpcall(require, "hypr.border-fx")tohyprland.lua. Uninstalling the plugin removed neither. Thepcallswallowed the failure, the file was inert under real Hyprland (its guard was never true),hyprctl configerrorsstayed empty, and nothing on the desktop looked wrong — the file only did damage inside the scanner, months later. The user's own view of this was that their customizations had made the system unstable, which was not what happened.Worth weighing when choosing between the PRs: the scanner has to survive Lua that arrives in
~/.config/hypr/without the user ever reading it.Local workaround for anyone stuck on the release
A wrapper at
/usr/local/bin/omarchy-menu-keybindings(which precedes/usr/binin Hyprland's PATH;~/.local/bindoes not) that addsflock -nso repeat presses cannot stack, and a watchdog that kills only a run holding >60% of a core for 15s — so an idle open menu is never killed — usingsetsidpluskill -TERM -$pid, because theluais a grandchild inside the< <(lua …)process substitution and does not die with its parent shell.Filed by Claude Opus 5 via Claude Code.
fix(issue-1): stable monitor bases keyed by name, not transient id
schmunk42 commentedon Sep 8, 2026
Not a new root cause — the analysis above holds exactly. Three small additions after hitting this on 4.0.2-1 (previous confirmations in the thread are 4.0.0 / 4.0.1-1), and PR #7564 has been open 19 days.
1. A dated forensic trail exists in the cache directory. Every failed refresh leaves a zero-byte
~/.cache/omarchy/keybindings.XXXXXXbehind, becauserefresh_keybindings_cache()mktemps before calling the scan and onlyrms on the error path — which is never reached when the scan hangs instead of failing. They accumulate one per attempt and keep their mtime:That is useful diagnostically: the oldest one matched the mtime of the backup of the config file I had edited, which pinned the offending line to the minute. Worth knowing when someone reports "load is high and I don't know since when" — the answer is sitting in the cache directory, and
ls -l ~/.cache/omarchy/keybindings.??????is a one-line check for whether a machine is affected at all.2. The last
.recordsfile makes the failure quiet for a while.output_binding_records()serves the cache whenever the hash ofhyprctl bindsstill matches, so the menu keeps working normally until the user's next binding change invalidates it. Between introducing the loop and the next binding edit, nothing is wrong from the user's point of view. In my case the two events were days apart, and what actually reached me was ambient CPU load with three orphanedluaprocesses (oldest 21 h) — the menu was never the suspect.3.
__lenon the stub does not fix it. Recording the dead end so nobody proposes it:ipairsdoes not consult__len; it counts up until__indexreturns nil. Verified on Lua 5.5.1 — with__len = function() return 0 endthe stub reports#noop == 0and the loop still runs forever. PR #7564's numeric-nil approach is the right shape.BigKelLearns commentedon Sep 12, 2026
Confirmed still live on 4.0.3-1 (Hyprland 0.56.2-2, Lua 5.5.1-1, kernel 7.2.3-arch1-3), so this is not a stale report. Adding three things the thread does not have yet.
1. The trigger was a hand-written personal file, not a third-party module.
The confirmations so far are a synthetic one-liner and plugin-authored configs (hyprsplit, a generated
border-fx.lua). Mine was ordinary user config:~/.config/hypr/looknfeel.luadoing a monitor check at load time.Worth adding to the repro, because it is the shape a user actually writes from the monitor docs, and it shows the
breakcannot save you either:monitor.name:match(...)also returns the truthy sentinel, so the guard condition never fires. Anyone iterating a list getter is exposed, not just plugin authors.2. The failure mode between the two already reported.
The thread has a CPU-spin-only repro and a full OOM/reboot. Mine ran in between for ~35 hours:
luaprocesses, oldest spinning ~35 h, each ~33% of a core (~1.3 cores total)That is the case for the timeout specifically: if the body does not allocate, the machine degrades quietly for days instead of crashing loudly.
3. The
keybindings.*cache-marker triage works.19 zero-byte markers in
~/.cache/omarchy/, oldest 2026-09-07 18:28, mtimes matching each leaked process start (keybindings.zvpKpyandkeybindings.atuffpat 09:49/09:50,keybindings.YDCDhWat 19:48 on 09-10). The.recordscache from 09-08 explains why the menu kept working until the binding hash changed. That is a genuinely useful one-line triage for "load is high and I do not know since when" and it deserves to stay in this thread.Workaround, verified in a different shape from the
rawgetprobe. Bounding the iteration by count:#on the stub is 0, so the loop is skipped under the scan and behaves identically against the real API.With that,
omarchy-menu-keybindings --printexits 0 and emits all 213 bind records on this machine, and a standalone run of the scanner's inline Lua returns 0.luac -pis clean across all personal config files. Either fix works, but the timeout + memory cap requested above is what turns this class from a silent permanent leak into a degraded menu.victorhramos-dev commentedon Sep 19, 2026
+1 — confirming same root cause on i9-13900HX laptop
Trigger: SUPER+K (keybindings menu). Triggered it twice in quick succession (probably auto-repeat / debounce race). Two
luaprocesses got stuck at 100% CPU each and never exited.Symptoms noticed: CPU package 96 °C / Core 20 97 °C reported by
sensorsand Mission Center. i9-13900HX withcrit=100 °C— 4 °C from hardware shutdown. Initially misread as a sensor/widget bug; turned out the temp was real, two runaway lua processes were the cause.Stuck process tree (11 h uptime before I noticed):
pipe:[85580661]andpipe:[85605819]were the lua stdin pipes — both had a reader but no writer: bash closed its end after the heredoc was consumed, but lua never saw EOF because it was spinning inipairsover thenooptable.Per-process fd count: ~70 fds each, all
anon_inode:sync_file— one perrequire()d module. Consistent withdofile()having walked the whole config tree and getting stuck mid-iteration.What in my config triggers it:
~/.config/hypr/hyprland.luadoesrequire("hypr.chatgpt_pet"), andchatgpt_pet.luaends with:In the menu script's stub,
hl.get_windowsresolves through the__indexmetamethod tonoop(a table whose own__indexreturnsnoop, notnil).ipairstherefore never reaches the array-end sentinel and loops forever — matches #8819 / #9395 / #12124 root cause exactly.Confirmed workaround:
kill -9 <lua_pids> <bash_ppids>. CPU dropped 96 °C → 65 °C in 5 s. Apgrep -af luawatch would catch it earlier next time.Suggestion: change the
__indexmetamethod in the stub (around line 145 ofbin/omarchy-menu-keybindings) fromreturn nooptoreturn nil, and additionally return a function-typed__indexthat errors loudly on any unexpected access during the scan so future regressions fail fast instead of pegging the CPU.omarchybot commentedon Oct 8, 2026
Re-checked against current
quattro(902fd8a): the defect is still there. Thenoopsentinel atbin/omarchy-menu-keybindings:187still answers every key, numeric ones included, with itself, and nothing in the two later changes to that file (07fccef, f814639) bounds the scan. The thread's confirmations on 4.0.2-1 and 4.0.3-1 match that.#7564 is now the one open fix. #8876 was closed by its author in favour of it. #7564 already lists this issue as one it closes, so this one closes when it merges. It is mergeable against
quattroand was verified on a disposable Omarchy VM as its own item: theipairsloop overhl.get_monitors(),hl.get_windows()andhl.get_loaded_plugins()returns at once, a user binding declared after the loop is recovered, and the cache version is bumped tov15so a cache written by the broken scan is rebuilt rather than served.What #7564 leaves open, as raised earlier in this thread: there is still no lock, so repeated presses each start their own scan, each now bounded to 10 seconds by the timeout. A scan the timeout kills is still cached as if it had finished, so bindings declared after a
while-style loop go missing without a message until the cache is next invalidated.This is the earliest report of the defect. Later reports of the same defect are still open, among them #12052, #12124, #12852, #13979, #14243 and #14302. Which fix to take, and how to consolidate those reports, is the maintainer's decision.
Checked by Claude Opus 5.5, with a second opinion from Codex at medium effort. Codex independently reproduced the hang on Lua 5.5.1 for all three getters, confirmed that #7564's diff ends each one, and confirmed that a timed-out scan still returns success and is cached. Where it only agrees with what this thread already said, its independence is not guaranteed.
aholbreich commentedon Oct 9, 2026
I compared the open fixes for this hang on current
quattro(c352b62d). Each PR was merged onto it and the real script was run against a stubbedhyprctlwith six configs. There are two separate bugs, and only #7564 fixes both:ipairsover the stub never ends). It's live on every release, since none shipsqconsole.luayet.quattro, the scan dies indefault/hypr/qconsole.lua:123(mon.scale > 0on the stub).omarchy.luarequires it before the user's files, so every bind inhypr/bindings.luais listed without a dispatcher, and that crash also hides bug 1 for configs that follow the template.ipairsinmonitors.luaipairsconfigwhile true do endquattro#13276 is the same stub hardening as #7564 without a timeout or a test. #14128 gets one line further in
qconsole.luaand then fails onmonitor.transform % 2. All new tests fail onquattroand pass on their branches.#12852 is a different cause (nested
hl.dspexpressions doubling in size), and only #12916 handles it: a 40-level nest ran to my 4 GB limit in about 2 s on every other branch, while #12916 stopped it in 27 ms. #7564's timeout bounds time, not memory, so #12916's caps are worth keeping as a follow-up on top of #7564; the two conflict textually.So: #7564 for this issue and #12052, #12124, #13979, #14243, #14302, with #12916 rebased after it for #12852. @jandrusk, this is why I'd pick #7564 over #12712: #12712 fixes the hang but leaves the
quattrodispatcher loss and plain runaway loops.omarchybot commentedon Oct 11, 2026
A correction to my last comment, and a change since it. I re-checked against current
quattro(9830142): the defect is still there. Thenoopstub atbin/omarchy-menu-keybindings:209still answers numeric keys with itself, and theluascan at:106still has no timeout.#7564 is not the only open fix, as I said on 8 October. #12712 (@wulujia) was already open then and still is. It changes only the stub's
__indexso that numeric keys returnnil, which ends theipairsloop this issue reports, and it merges cleanly intoquattro. #13276 makes a similar stub change and no longer merges cleanly. #12712 lists no issue it closes, so merging it would leave this one open. #7564 still lists this issue.#7564 no longer merges cleanly into
quattro. e1b0e5e (#14431) changed the scan's invocation tolua -E -so thatLUA_INITandLUA_PATHstay out of it. #7564 changes the same line totimeout 10 lua. That is the only conflicting hunk, and keeping both givestimeout 10 lua -E -. Its testtest/shell.d/menu-keybindings-lua-scan-test.shmatches the old invocation literally on lines 16 and 56, so it needs the same update or it fails after the merge.Which fix to take is the maintainer's decision. This issue is waiting on that.
Checked by Claude Opus 5.5 from the source, with no code run, and a second opinion from Codex at medium effort. Codex agreed on the defect and the conflict, though its independence is not guaranteed. It also found that #7564's test would break, which I then confirmed.