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

bt-agent.service never accepts device-initiated pairing (bluez-tools bt-agent rejects RequestAuthorization): Xbox controller shows Connected but never bonds

Reported by spizia · filed Sep 8, 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

bt-agent.service never accepts device-initiated pairing (bluez-tools bt-agent rejects RequestAuthorization): Xbox controller shows Connected but never bondsrevision 1 · captured 2026-09-28 21:23 UTC via poll

System details

Intel Core i7-10750H, Intel UHD Graphics + NVIDIA RTX 3070 Mobile, Intel AX201 Bluetooth. Omarchy 4.0.2 (also present on the quattro branch), kernel 7.1.9-arch1-2, bluez 5.87-2, bluez-tools 0.2.0-6.

What's wrong?

bt-agent.service (default/systemd/user/bt-agent.service) runs bluez-tools' bt-agent -c NoInputNoOutput and its comment says "NoInputNoOutput auto-accepts pair requests". It does not. bluez-tools' bt-agent never accepts a RequestAuthorization call: in interactive mode it prints Authorize this device pairing (yes/no)? and reads stdin, which is /dev/null under systemd, so scanf hits EOF and it answers org.bluez.Error.Rejected; in -d / pin-file mode it rejects unconditionally (see agent_method_request_authorization in bluez-tools src/lib/agent-helper.c, and the man page: "Any devices that attempt to pair without a valid passkey defined in the pin file will be automatically rejected"). Only RequestConfirmation with a pin-file wildcard can auto-accept, and BlueZ uses RequestAuthorization, not RequestConfirmation, for device-initiated Just Works pairing.

So every device that initiates pairing itself gets rejected. Any BLE HID device that sends an SMP Security Request after connecting hits this: an Xbox Wireless Controller (model 1914, BLE) does it on every connect, and also whenever it has been re-paired with an Xbox/PC in between and wiped its bond.

Observed on this machine (Xbox Wireless Controller 0C:35:26:40:DD:6D):

$ journalctl --user -t bt-agent
bt-agent[1116]: Device: Xbox Wireless Controller (0C:35:26:40:DD:6D)
bt-agent[1116]: Authorize this device pairing (yes/no)? Device: Xbox Wireless Controller (0C:35:26:40:DD:6D)
bt-agent[1116]: Authorize this device pairing (yes/no)? Device: Xbox Wireless Controller (0C:35:26:40:DD:6D)
...   (one line every ~3 s for two minutes: the controller reconnects and asks again each time)

$ journalctl -u bluetooth
bluetoothd[937]: profiles/input/hog-lib.c:info_read_cb() HID Information read failed: Request attribute has encountered an unlikely error
bluetoothd[937]: profiles/input/hog-lib.c:report_reference_cb() Read Report Reference descriptor failed: Request attribute has encountered an unlikely error

$ bluetoothctl info 0C:35:26:40:DD:6D
        Paired: no
        Bonded: no
        Trusted: yes
        Connected: yes

/proc/1116/fd/0 -> /dev/null for the agent process. The GATT reads fail with ATT 0x0E because the link never gets encrypted (no bond), so the controller keeps blinking in pairing mode and no input ever reaches the kernel, while the Bluetooth panel shows it under CONNECTED.

Steps to reproduce

  1. Omarchy 4.0.2 with the stock bt-agent.service running.
  2. Put an Xbox Wireless Controller (Series X|S, BLE) in pairing mode and select it in the Bluetooth panel.
  3. Panel shows Connected, controller keeps blinking, no game sees it. bluetoothctl info shows Paired: no, Bonded: no, Trusted: yes. The journal shows the bt-agent lines above.

Aggravating factor in `bin/omarchy-bluetooth-device`: the pair) case runs bluetoothctl pair ... || true, then trust_device unconditionally, then connect. When pairing fails the device still ends up trusted, the panel then files it under "known" and only ever runs connect for it, so the user can never trigger pair again from the UI; the only way out is Forget. Trusting only after a successful pair would avoid the stuck state.

Verified fix: replacing the agent with one that actually accepts. With a small Gio/python agent registered as default with NoInputNoOutput capability that returns success from RequestAuthorization / RequestConfirmation / AuthorizeService (installed via a drop-in overriding ExecStart), the controller bonded on the next Security Request (RequestAuthorization Xbox Wireless Controller ... -> accepted in the agent log, Bonded: yes, HID input flowing). bluetoothctl --agent NoInputNoOutput has the same problem as bt-agent (it prompts), so it is not a drop-in alternative; a minimal agent is needed. Happy to send the one I'm running as a PR if that's the direction you want.

Minimal auto-accept agent used to verify (python3 + Gio, no extra deps)
#!/usr/bin/env python3
import gi
gi.require_version("Gio", "2.0")
from gi.repository import Gio, GLib

PATH = "/local/bt_auto_agent"
XML = """<node><interface name="org.bluez.Agent1">
 <method name="Release"/>
 <method name="RequestPinCode"><arg type="o" direction="in"/><arg type="s" direction="out"/></method>
 <method name="DisplayPinCode"><arg type="o" direction="in"/><arg type="s" direction="in"/></method>
 <method name="RequestPasskey"><arg type="o" direction="in"/><arg type="u" direction="out"/></method>
 <method name="DisplayPasskey"><arg type="o" direction="in"/><arg type="u" direction="in"/><arg type="q" direction="in"/></method>
 <method name="RequestConfirmation"><arg type="o" direction="in"/><arg type="u" direction="in"/></method>
 <method name="RequestAuthorization"><arg type="o" direction="in"/></method>
 <method name="AuthorizeService"><arg type="o" direction="in"/><arg type="s" direction="in"/></method>
 <method name="Cancel"/>
</interface></node>"""

def on_call(conn, sender, path, iface, method, params, inv):
    if method == "RequestPinCode":
        inv.return_value(GLib.Variant("(s)", ("0000",)))
    elif method == "RequestPasskey":
        inv.return_value(GLib.Variant("(u)", (0,)))
    else:
        inv.return_value(None)  # accept everything else

bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
bus.register_object_with_closures2(PATH, Gio.DBusNodeInfo.new_for_xml(XML).interfaces[0], on_call, None, None)
def register(*_):
    for m, a in (("RegisterAgent", GLib.Variant("(os)", (PATH, "NoInputNoOutput"))),
                 ("RequestDefaultAgent", GLib.Variant("(o)", (PATH,)))):
        bus.call_sync("org.bluez", "/org/bluez", "org.bluez.AgentManager1", m, a, None, Gio.DBusCallFlags.NONE, 5000, None)
Gio.bus_watch_name_on_connection(bus, "org.bluez", Gio.BusNameWatcherFlags.NONE, register, None)
GLib.MainLoop().run()

Full omarchy-debug output available on request; the relevant journal lines are above.

View raw text
### System details

Intel Core i7-10750H, Intel UHD Graphics + NVIDIA RTX 3070 Mobile, Intel AX201 Bluetooth. Omarchy 4.0.2 (also present on the `quattro` branch), kernel 7.1.9-arch1-2, bluez 5.87-2, bluez-tools 0.2.0-6.

### What's wrong?

`bt-agent.service` (`default/systemd/user/bt-agent.service`) runs bluez-tools' `bt-agent -c NoInputNoOutput` and its comment says "NoInputNoOutput auto-accepts pair requests". It does not. bluez-tools' `bt-agent` never accepts a `RequestAuthorization` call: in interactive mode it prints `Authorize this device pairing (yes/no)?` and reads stdin, which is `/dev/null` under systemd, so `scanf` hits EOF and it answers `org.bluez.Error.Rejected`; in `-d` / pin-file mode it rejects unconditionally (see `agent_method_request_authorization` in bluez-tools `src/lib/agent-helper.c`, and the man page: "Any devices that attempt to pair without a valid passkey defined in the pin file will be automatically rejected"). Only `RequestConfirmation` with a pin-file wildcard can auto-accept, and BlueZ uses `RequestAuthorization`, not `RequestConfirmation`, for device-initiated Just Works pairing.

So every device that initiates pairing itself gets rejected. Any BLE HID device that sends an SMP Security Request after connecting hits this: an Xbox Wireless Controller (model 1914, BLE) does it on every connect, and also whenever it has been re-paired with an Xbox/PC in between and wiped its bond.

**Observed on this machine** (Xbox Wireless Controller 0C:35:26:40:DD:6D):

```
$ journalctl --user -t bt-agent
bt-agent[1116]: Device: Xbox Wireless Controller (0C:35:26:40:DD:6D)
bt-agent[1116]: Authorize this device pairing (yes/no)? Device: Xbox Wireless Controller (0C:35:26:40:DD:6D)
bt-agent[1116]: Authorize this device pairing (yes/no)? Device: Xbox Wireless Controller (0C:35:26:40:DD:6D)
...   (one line every ~3 s for two minutes: the controller reconnects and asks again each time)

$ journalctl -u bluetooth
bluetoothd[937]: profiles/input/hog-lib.c:info_read_cb() HID Information read failed: Request attribute has encountered an unlikely error
bluetoothd[937]: profiles/input/hog-lib.c:report_reference_cb() Read Report Reference descriptor failed: Request attribute has encountered an unlikely error

$ bluetoothctl info 0C:35:26:40:DD:6D
        Paired: no
        Bonded: no
        Trusted: yes
        Connected: yes
```

`/proc/1116/fd/0 -> /dev/null` for the agent process. The GATT reads fail with ATT 0x0E because the link never gets encrypted (no bond), so the controller keeps blinking in pairing mode and no input ever reaches the kernel, while the Bluetooth panel shows it under CONNECTED.

**Steps to reproduce**

1. Omarchy 4.0.2 with the stock `bt-agent.service` running.
2. Put an Xbox Wireless Controller (Series X|S, BLE) in pairing mode and select it in the Bluetooth panel.
3. Panel shows Connected, controller keeps blinking, no game sees it. `bluetoothctl info` shows `Paired: no`, `Bonded: no`, `Trusted: yes`. The journal shows the `bt-agent` lines above.

**Aggravating factor in `bin/omarchy-bluetooth-device`**: the `pair)` case runs `bluetoothctl pair ... || true`, then `trust_device` unconditionally, then `connect`. When pairing fails the device still ends up trusted, the panel then files it under "known" and only ever runs `connect` for it, so the user can never trigger `pair` again from the UI; the only way out is Forget. Trusting only after a successful pair would avoid the stuck state.

**Verified fix**: replacing the agent with one that actually accepts. With a small Gio/python agent registered as default with `NoInputNoOutput` capability that returns success from `RequestAuthorization` / `RequestConfirmation` / `AuthorizeService` (installed via a drop-in overriding `ExecStart`), the controller bonded on the next Security Request (`RequestAuthorization Xbox Wireless Controller ... -> accepted` in the agent log, `Bonded: yes`, HID input flowing). `bluetoothctl --agent NoInputNoOutput` has the same problem as bt-agent (it prompts), so it is not a drop-in alternative; a minimal agent is needed. Happy to send the one I'm running as a PR if that's the direction you want.

<details>
<summary>Minimal auto-accept agent used to verify (python3 + Gio, no extra deps)</summary>

```python
#!/usr/bin/env python3
import gi
gi.require_version("Gio", "2.0")
from gi.repository import Gio, GLib

PATH = "/local/bt_auto_agent"
XML = """<node><interface name="org.bluez.Agent1">
 <method name="Release"/>
 <method name="RequestPinCode"><arg type="o" direction="in"/><arg type="s" direction="out"/></method>
 <method name="DisplayPinCode"><arg type="o" direction="in"/><arg type="s" direction="in"/></method>
 <method name="RequestPasskey"><arg type="o" direction="in"/><arg type="u" direction="out"/></method>
 <method name="DisplayPasskey"><arg type="o" direction="in"/><arg type="u" direction="in"/><arg type="q" direction="in"/></method>
 <method name="RequestConfirmation"><arg type="o" direction="in"/><arg type="u" direction="in"/></method>
 <method name="RequestAuthorization"><arg type="o" direction="in"/></method>
 <method name="AuthorizeService"><arg type="o" direction="in"/><arg type="s" direction="in"/></method>
 <method name="Cancel"/>
</interface></node>"""

def on_call(conn, sender, path, iface, method, params, inv):
    if method == "RequestPinCode":
        inv.return_value(GLib.Variant("(s)", ("0000",)))
    elif method == "RequestPasskey":
        inv.return_value(GLib.Variant("(u)", (0,)))
    else:
        inv.return_value(None)  # accept everything else

bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
bus.register_object_with_closures2(PATH, Gio.DBusNodeInfo.new_for_xml(XML).interfaces[0], on_call, None, None)
def register(*_):
    for m, a in (("RegisterAgent", GLib.Variant("(os)", (PATH, "NoInputNoOutput"))),
                 ("RequestDefaultAgent", GLib.Variant("(o)", (PATH,)))):
        bus.call_sync("org.bluez", "/org/bluez", "org.bluez.AgentManager1", m, a, None, Gio.DBusCallFlags.NONE, 5000, None)
Gio.bus_watch_name_on_connection(bus, "org.bluez", Gio.BusNameWatcherFlags.NONE, register, None)
GLib.MainLoop().run()
```
</details>

Full `omarchy-debug` output available on request; the relevant journal lines are above.

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 11:08 UTC