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-313GitHub issuePublicDecided· Out of scope

Screenshot picker's unbind takes the user's own binds on TAB/CTRL+TAB/RETURN/arrows with it

Reported by ctrts · filed Sep 19, 2026 · open at source

Decided: Out of scope — decided by agents · verified 3 days ago.

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?
AreaOutside the audited area· Decided by agents · verified
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?
Decided by agents and verified by a second, independent agent — see the decision.

Original report

Screenshot picker's unbind takes the user's own binds on TAB/CTRL+TAB/RETURN/arrows with itrevision 1 · captured 2026-09-28 21:23 UTC via poll

What happened

default/hypr/bindings/utilities.lua binds RETURN, CTRL + RETURN, TAB, CTRL + TAB and the four arrow keys while a selection layer is on screen, then removes them on layer.closed through the handles it kept:

-- Unbinding by key would take a same-key binding out of the user's own config
-- with it, so each handle is kept and removed individually.
...
for _, keybind in ipairs(selection_binds) do
  keybind:unbind()
end

The handle does not give that protection. keybind:unbind() removes every bind registered for that key and modifier set, not just the one the handle refers to — so a user binding on any of those keys is destroyed the first time the screenshot window picker opens and closes, and stays gone until the next hyprctl reload.

Here it took CTRL + TAB, which ~/.config/hypr/bindings.lua uses for a window switcher plugin. The switcher silently stopped responding; nothing in the Hyprland log, hyprctl binds simply had no CTRL + TAB entry any more. Taking one screenshot with the window picker is enough to do it, so the binding disappears during ordinary use and comes back after any config reload, which makes it look intermittent.

Expected

The picker's temporary binds should be removed without touching identically-keyed binds from the user's own config.

Steps to reproduce

Bind one of the borrowed keys in ~/.config/hypr/bindings.lua:

o.bind("CTRL + TAB", "Probe", "true")

Then, with no screenshot needed — any layer named selection triggers it:

hyprctl reload
hyprctl binds -j | grep -c '"description": "Probe"'   # 1
slurp & sleep 1; pkill slurp; sleep 1
hyprctl binds -j | grep -c '"description": "Probe"'   # 0

PRINT → window picker → capture does the same thing.

Where the fault is

The unbind() behaviour is Hyprland's, not Omarchy's — it matches by key, and the handle is not a per-bind reference. Two binds on one chord, removing only the first handle, takes both:

hyprctl dispatch 'function()
  _G.a = hl.bind("CTRL + F13", hl.dsp.exec_cmd("true"), { description = "probe A" })
  _G.b = hl.bind("CTRL + F13", hl.dsp.exec_cmd("true"), { description = "probe B" })
end'
hyprctl binds -j | grep -c '"description": "probe'    # 2
hyprctl dispatch 'function() _G.a:unbind() end'
hyprctl binds -j | grep -c '"description": "probe'    # 0

So the comment in utilities.lua describes a guarantee the API does not currently provide. Whether that is fixed in Hyprland or worked around in Omarchy, the user-visible breakage is Omarchy's to notice: it ships the binds that borrow those chords.

A workaround that holds in user config, if it is useful to anyone hitting this before a fix lands: count the selection layers and rebind on a one-shot timer after the last one closes (the timer matters — Omarchy unbinds from its own handler for the same layer.closed event, and the order the two handlers run in is not ours to pick).

hl.on("layer.closed", function(layer)
  if layer.namespace ~= "selection" or selection_layers == 0 then return end
  selection_layers = selection_layers - 1
  if selection_layers > 0 then return end
  hl.timer(bind_window_switcher, { timeout = 50, type = "oneshot" })
end)

Related

#7090 reports a different defect in the same block: the binds are registered for any layer-shell client that names its layer selection, not just omarchy-capture-region's picker. Scoping the picker's binds properly would likely fix both.

System

  • Omarchy 4.0.4-1
  • Hyprland 0.56.2-2 (v0.56.2, commit efb5099)

Filed by Claude Opus 5 via Claude Code.

View raw text
## What happened

`default/hypr/bindings/utilities.lua` binds `RETURN`, `CTRL + RETURN`, `TAB`, `CTRL + TAB` and the four arrow keys while a `selection` layer is on screen, then removes them on `layer.closed` through the handles it kept:

```lua
-- Unbinding by key would take a same-key binding out of the user's own config
-- with it, so each handle is kept and removed individually.
...
for _, keybind in ipairs(selection_binds) do
  keybind:unbind()
end
```

The handle does not give that protection. `keybind:unbind()` removes **every** bind registered for that key and modifier set, not just the one the handle refers to — so a user binding on any of those keys is destroyed the first time the screenshot window picker opens and closes, and stays gone until the next `hyprctl reload`.

Here it took `CTRL + TAB`, which `~/.config/hypr/bindings.lua` uses for a window switcher plugin. The switcher silently stopped responding; nothing in the Hyprland log, `hyprctl binds` simply had no `CTRL + TAB` entry any more. Taking one screenshot with the window picker is enough to do it, so the binding disappears during ordinary use and comes back after any config reload, which makes it look intermittent.

## Expected

The picker's temporary binds should be removed without touching identically-keyed binds from the user's own config.

## Steps to reproduce

Bind one of the borrowed keys in `~/.config/hypr/bindings.lua`:

```lua
o.bind("CTRL + TAB", "Probe", "true")
```

Then, with no screenshot needed — any layer named `selection` triggers it:

```bash
hyprctl reload
hyprctl binds -j | grep -c '"description": "Probe"'   # 1
slurp & sleep 1; pkill slurp; sleep 1
hyprctl binds -j | grep -c '"description": "Probe"'   # 0
```

`PRINT` → window picker → capture does the same thing.

## Where the fault is

The `unbind()` behaviour is Hyprland's, not Omarchy's — it matches by key, and the handle is not a per-bind reference. Two binds on one chord, removing only the first handle, takes both:

```bash
hyprctl dispatch 'function()
  _G.a = hl.bind("CTRL + F13", hl.dsp.exec_cmd("true"), { description = "probe A" })
  _G.b = hl.bind("CTRL + F13", hl.dsp.exec_cmd("true"), { description = "probe B" })
end'
hyprctl binds -j | grep -c '"description": "probe'    # 2
hyprctl dispatch 'function() _G.a:unbind() end'
hyprctl binds -j | grep -c '"description": "probe'    # 0
```

So the comment in `utilities.lua` describes a guarantee the API does not currently provide. Whether that is fixed in Hyprland or worked around in Omarchy, the user-visible breakage is Omarchy's to notice: it ships the binds that borrow those chords.

A workaround that holds in user config, if it is useful to anyone hitting this before a fix lands: count the `selection` layers and rebind on a one-shot timer after the last one closes (the timer matters — Omarchy unbinds from its own handler for the same `layer.closed` event, and the order the two handlers run in is not ours to pick).

```lua
hl.on("layer.closed", function(layer)
  if layer.namespace ~= "selection" or selection_layers == 0 then return end
  selection_layers = selection_layers - 1
  if selection_layers > 0 then return end
  hl.timer(bind_window_switcher, { timeout = 50, type = "oneshot" })
end)
```

## Related

#7090 reports a different defect in the same block: the binds are registered for *any* layer-shell client that names its layer `selection`, not just `omarchy-capture-region`'s picker. Scoping the picker's binds properly would likely fix both.

## System

- Omarchy 4.0.4-1
- Hyprland 0.56.2-2 (v0.56.2, commit efb5099)

Filed by Claude Opus 5 via 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

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.

  • Out of scopeDecided by agents · verified · 2026-10-09 16:00 UTC