Skip to content

[Bug]: MX Anywhere 3S Bluetooth probe drops one feature response and is misclassified as receiver interface #468

Description

@garethng

Pre-flight checklist

Which part of OpenLogi?

Both

OpenLogi version

v0.6.22 release, exact tag/commit:

v0.6.22
b6aaf468d2a7a84c2579955c9b9287c4b4c7dd21

Operating system

macOS

OS version & architecture

macOS 27.0, Apple Silicon (arm64)

Device model

Logitech MX Anywhere 3S, VID 046d, PID b037

How is the device connected?

Bluetooth (direct, no receiver)

Affected area(s)

  • Device discovery / detection
  • Button remapping
  • DPI control
  • SmartShift
  • Battery status
  • Other: scroll inversion and any setting that requires an online HID++ route

What happened?

The MX Anywhere 3S is paired and works as a normal macOS mouse, but OpenLogi usually shows it as offline or reports zero detected devices. Settings such as reverse scrolling do not take effect because the agent never establishes a usable inventory/control route.

The device is supported and permissions are not the blocker:

  • macOS exposes the expected Logitech HID++ long-report collection, usage page 0xff43, usage 0x0202.
  • OpenLogi opens that interface successfully.
  • Accessibility and Input Monitoring are granted to the signed nested OpenLogiAgent.app.
  • Many HID++ requests return normally in about 15–30 ms.
  • A standalone openlogi list trace eventually completed the same 35-entry feature walk and correctly identified the mouse, including battery and BTLE transport.

The failure occurs when any one request during the feature-table walk receives no response. The failed slot varies between attempts. enumerate_features() returns an error, probe_features() discards all features already read, and probe_direct() then classifies the Bluetooth-direct mouse as a likely receiver secondary interface:

enumerate_features failed slot=255 error=Channel(Timeout)
slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=b037 has_model=false

The persistent agent then evicts/reopens the channel and repeats this indefinitely. The GUI retains a saved device record but marks it offline, or reports no device.

This makes a recoverable dropped BLE HID++ response become a complete device-discovery failure. It also explains why killing/restarting the agent or rescanning sometimes appears to fix the device: a later attempt may happen to complete every request.

Expected behavior

A single feature-table request timeout should be retried or tolerated. A Bluetooth-direct node that was opened successfully and returned valid HID++ responses should not be discarded and described as a receiver secondary interface solely because one later feature-table request timed out.

At minimum, OpenLogi should retain/replay the last known direct-device identity and capabilities while retrying the failed request.

Steps to reproduce

  1. Pair an MX Anywhere 3S directly through macOS Bluetooth.

  2. Fully quit/uninstall Logi Options+ and ensure no competing Logitech process is running.

  3. Grant Accessibility and Input Monitoring to OpenLogi Agent.

  4. Quit the GUI and agent.

  5. Run:

    OPENLOGI_LOG=trace /Applications/OpenLogi.app/Contents/MacOS/openlogi list
  6. Repeat if necessary. Some attempts read part of the 35-entry feature table and then time out; another attempt may complete.

  7. Start openlogi-agent with OPENLOGI_LOG=trace. In this environment it repeatedly aborts the feature walk, evicts the channel, and never logs BT-direct / wired device recognised.

  8. Reopen the GUI. The saved MX Anywhere 3S remains offline and device settings do not take effect.

Diagnostics report

### OpenLogi Diagnostics

**App**
- OpenLogi (GUI): v0.6.22 (release)
- Agent: v0.6.22 (connected)
- IPC protocol: GUI 10 / agent 10
- Inventory: ready
- OS: macOS 27.0 (arm64)
- Locale: en-CN (UI: follow system)
- Accessibility: granted · Input hook: installed
- Launch at login: no · Menu bar: yes · Update check: off
- Running from: source build (dev)
- Config: schema 3 · 0 configured device(s) · thumbwheel 14

**Assets**
- Source: user cache · Index: loaded (210 models) · User cache: present
- Cache path: ~/.local/share/openlogi/assets · Bundle assets: absent

**Devices (0)**
- No devices detected.

openlogi list output

An exclusive trace demonstrates that the exact same device can eventually be identified when one retry completes:

DEBUG openlogi_hid::transport: logitech HID node name=MX Anywhere 3S pid=b037 usage_page=0xff43 usage_id=0x0202 matched=true
DEBUG openlogi_hid::inventory: HID++ candidate interfaces count=1
DEBUG openlogi_hid::transport: opened HID++ channel name=MX Anywhere 3S vid=046d
...
TRACE hidpp::device: feature index=255 slot=35 id=0x9205 version=0
...
DEBUG openlogi_hid::inventory::probe: BT-direct / wired device recognised name=MX Anywhere 3S
MX Anywhere 3S (—, vid=046d pid=b037)
  └─ slot 255 ● MX Anywhere 3S (mouse, wpid=?, battery=40% good (discharging))
          model_ids=[b037,0000,0000] ext=00 serial=[redacted] unit_id=[redacted] transports=btle

Earlier retries in that same process failed:

TRACE hidpp::channel: hidpp no response dev=255 feat=2 error=Timeout
...
TRACE hidpp::channel: hidpp no response dev=255 feat=1 error=Timeout
DEBUG openlogi_hid::inventory::features: enumerate_features failed slot=255 error=Channel(Timeout)
DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=b037 has_model=false

Detailed persistent-agent trace

The agent was launched alone (GUI closed), so this happens before gesture/control capture becomes active.

Attempt 1 reached slot 12, then timed out:

TRACE hidpp::device: enumerating feature table index=255 count=35
TRACE hidpp::device: feature index=255 slot=10 id=0x1b04 version=5
TRACE hidpp::device: feature index=255 slot=11 id=0x1814 version=1
TRACE hidpp::device: feature index=255 slot=12 id=0x1815 version=2
TRACE hidpp::channel: hidpp request dev=255 feat=1 func=17
# approximately five seconds
TRACE hidpp::channel: hidpp no response dev=255 feat=1 error=Timeout
DEBUG openlogi_hid::inventory::features: enumerate_features failed slot=255 error=Channel(Timeout)
DEBUG openlogi_hid::inventory::probe: slot 0xff exposes no battery or config feature — likely a receiver secondary interface; skipping vid=046d pid=b037 has_model=false

A later attempt failed immediately after slot 4:

TRACE hidpp::device: enumerating feature table index=255 count=35
TRACE hidpp::device: feature index=255 slot=1 id=0x0001 version=2
TRACE hidpp::device: feature index=255 slot=2 id=0x0003 version=4
TRACE hidpp::device: feature index=255 slot=3 id=0x0005 version=0
TRACE hidpp::device: feature index=255 slot=4 id=0x1d4b version=0
TRACE hidpp::channel: hidpp request dev=255 feat=1 func=17
# approximately five seconds
TRACE hidpp::channel: hidpp no response dev=255 feat=1 error=Timeout
DEBUG openlogi_hid::inventory::features: enumerate_features failed slot=255 error=Channel(Timeout)

Another reached slot 20 before failing:

TRACE hidpp::device: feature index=255 slot=17 id=0x2251 version=0
TRACE hidpp::device: feature index=255 slot=18 id=0x00c3 version=0
TRACE hidpp::device: feature index=255 slot=19 id=0x1802 version=0
TRACE hidpp::device: feature index=255 slot=20 id=0x1803 version=0
TRACE hidpp::channel: hidpp request dev=255 feat=1 func=17
# approximately five seconds
TRACE hidpp::channel: hidpp no response dev=255 feat=1 error=Timeout

Observed failure points across consecutive attempts included immediately after slots 0, 1, 4, 6, 9, 10, 12, 15, 16, and 20. That varying position argues against one unsupported feature ID.

Some channel writes also returned:

TRACE hidpp::channel: hidpp no response dev=255 feat=0 error=Implementation(Message("report writer callback error: 0xE00002D6"))
DEBUG openlogi_hid::inventory::features: Device::new failed slot=255 error=Channel(Implementation(Message("report writer callback error: 0xE00002D6")))

Apple's local IOKit IOReturn.h defines 0xE00002D6 as kIOReturnTimeout (I/O Timeout), not kIOReturnNotPermitted.

After two failed ticks:

WARN openlogi_hid::inventory: node probe keeps failing — dropping its channel to reopen next tick

This cycle repeated for more than a minute without producing:

BT-direct / wired device recognised
control capture active

Therefore the initial failure is reproducible without GUI/agent contention and before the gesture capture watcher can open a second channel.

Relevant v0.6.22 code path

  1. crates/openlogi-hidpp/src/device.rs:147-149

    for i in 1..=count {
        let info = feature_set_feature.get_feature(i).await?;

    A single failed request aborts the full walk; there is no per-entry retry/backoff.

  2. crates/openlogi-hid/src/inventory/features.rs:91-101

    Any enumerate_features() error immediately returns ProbedFeatures::default(), discarding successfully read entries.

  3. crates/openlogi-hid/src/inventory/probe.rs:436-455

    Empty capabilities/battery make is_peripheral false, so the direct BLE mouse is logged as a likely receiver secondary interface and returned with no inventory.

Possible fix direction

  • Retry an individual feature-table request one or two times, with a short BLE-appropriate delay/backoff, rather than restarting the complete node probe.
  • Preserve the partial/last-known feature table and direct-device identity when a later entry times out.
  • Do not apply the receiver-secondary discriminator to a direct node when the feature walk itself failed; report it as a transient probe failure instead.
  • Avoid repeated open/close churn if channel eviction is amplifying the macOS IOHID timeout state.

Related: #159, #218, #254, #322, #348, and #454.

macOS permissions

  • OpenLogi Agent has Accessibility permission.
  • OpenLogi Agent has Input Monitoring permission.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions