Skip to content

fix(agent): hand off capture across receiver routes - #1477

Open
4ni1ak wants to merge 1 commit into
AprilNEA:masterfrom
4ni1ak:fix/multi-receiver-capture-route
Open

4ni1ak wants to merge 1 commit into
AprilNEA:masterfrom
4ni1ak:fix/multi-receiver-capture-route

Conversation

@4ni1ak

@4ni1ak 4ni1ak commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Rebase of #1198 (originally by @justinechang39, branch fix/multi-receiver-capture-route) onto current master, opened here because pushing directly to the original fork branch was denied (403) despite maintainerCanModify being set.

Fixes HID++ button capture becoming stuck when one physical mouse moves between receiver routes (e.g. two Bolt receivers). A retiring capture session now restores firmware state through the newly active route before allowing its successor to arm. Pending restoration retries also follow the latest route for the same physical device.

Changes

  • openlogi-device

    • Add typed shutdown and route-handoff capture commands (CaptureSessionStop, CaptureStop::Handoff).
    • Restore diverted controls through the successor receiver route (PendingCaptureRestore::retry_via).
    • Retain the latest route across delayed restoration retries.
    • Support rapid route changes back to the original receiver without leaving capture blocked.
  • openlogi-agent-core

    • Propagate route handoff intent through capture-session teardown (CaptureSession::reconcile_with).
    • Retry pending restoration through the latest capture plan for the same physical device.
    • Preserve the existing one-firmware-owner invariant until restoration completes.

No configuration or IPC wire-format changes.

Rebase notes

Master had meanwhile consolidated CaptureSession into a CaptureSlot<Target, Dispatch, Restore> wrapper (Running/Recovering) and renamed the manager's pending_restores map to slots (#1321-era work). Resolved by making CaptureSlot generic over the new Stop type parameter as well, and threading retry_via's route lookup through the slots-based structure instead of the pre-refactor flat map. Also updated a shutdown call site and a replay test (session_replay_tests.rs) that weren't part of the textual conflict but broke on CaptureSessionStop becoming the shutdown channel's payload type.

Testing

Full local gate on the rebased tip (Linux):

  • cargo fmt --all -- --check
  • RUSTFLAGS="-D warnings" cargo clippy --workspace --all-targets -- -D warnings
  • RUSTFLAGS="-D warnings" cargo test --workspace
  • RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --document-private-items --exclude openlogi-ui --exclude openlogi-desktop --exclude openlogi-overlay --exclude openlogi-agent

All green. The original PR's tests exercise the actual handoff scenario directly: a unit test proving reconcile_with emits Handoff(successor_route) on a route change, plus async integration tests for following a device to a new route, remembering a route that appears later, and bouncing back to the original route on a rapid switch. No additional test was needed beyond what #1198 already included.

Hardware verification status: not verified by anyone maintaining this fix. The original author reproduced the failure on an MX Master 4 with two Bolt receivers but has not hardware-verified this patch (no compatible Xcode/Metal toolchain for their test Mac). This rebase only re-validates the local gate; it does not add hardware verification.

Fixes #1196

@greptile-apps

greptile-apps Bot commented Sep 17, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

This PR is not safe to merge until shutdown restoration follows the latest capture plan rather than retrying an obsolete handoff route.

Fix All in CodexFindings

  1. P1 Shutdown retries obsolete route

Summary

This PR introduces typed gesture-capture stop commands so firmware restoration can follow a physical device between receiver routes while preserving exclusive firmware ownership.

  • Propagates route-handoff intent from agent capture reconciliation into device-session teardown.
  • Lets pending restoration tokens adopt and retain the latest route.
  • Adds unit and asynchronous coverage for receiver handoff, delayed route publication, and rapid route reversal.
  • The shutdown drain still discards the latest route plans, allowing a rapid multi-route transition to strand restoration on an obsolete route.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Capture active on route A] --> B[Plan changes to route B]
    B --> C[Send Handoff B and begin draining]
    C --> D[Plan changes again to route A or C]
    D --> E{Normal reconcile or shutdown?}
    E -->|Normal reconcile| F[retry_via latest plan route]
    F --> G[Restore firmware]
    G --> H[Allow successor capture]
    E -->|Shutdown before reconcile| I[Drain passes empty plan list]
    I --> J[Retry stored obsolete route B]
    J -->|Route B absent| K[Restore remains pending]
    K --> L[Replacement blocks or terminal exit times out]
Loading

Reviews (1) · Last reviewed commit: "fix(agent): hand off capture across rece..."

}
if let Some(_lease) = acquire_session_lease(receiver_access, &mut state.lease) {
retry_pending_restores(&mut state.slots, &channels.registry, Instant::now()).await;
retry_pending_restores(&mut state.slots, &channels.registry, &[], Instant::now()).await;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Shutdown retries obsolete route

If a device moves from route A to B and then back to A or onward to C while the first handoff is still draining, the restore token retains route B because draining sessions ignore later plan changes. This shutdown path passes an empty plan list, so it repeatedly retries obsolete route B instead of the latest reachable route. Confirmed replacement can remain blocked indefinitely, while terminal shutdown can time out and leave the controls diverted.

Knowledge Base Used:

Fix in Codex Fix in Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: button capture stalls when one mouse switches between Bolt receivers

2 participants