Repository navigation
Description
Summary
Taking a peripheral fails with "didn't release" whenever the Mac holding it needs more than about 2 seconds to finish releasing it. The holder is still mid-release at that point, so the taker gives up, and the release then completes with nobody picking the peripheral up. It ends up paired to neither Mac, and can stop answering until it is power-cycled.
Environment
- Magic Switch v2.30.1 (release build) on both Macs
- Mac A: MacBook Pro, Apple silicon, macOS 15.7.1
- Mac B: Mac mini, Apple silicon, macOS 26.7.1
- Magic Keyboard (product ID 0x0322), registered on both Macs through Sync
- Connected over the LAN, with Bonjour working and Ping succeeding
Steps to reproduce
- Have the keyboard connected to Mac B.
- Click Take on Mac A.
It is intermittent because it depends on how long Mac B's release takes. Mac B took 1.1s, 3.5s and 11.7s across the releases I timed, and only the 1.1s one worked.
Expected
The take waits for the release to finish, then pairs the keyboard on Mac A.
Actual
Mac A shows the take-failed notification about 2 seconds after the release starts, and the keyboard is left on neither Mac.
Timeline from one failure (Mac A and Mac B logs, device names removed):
15:10:34.275 Mac B app Device information removed: <keyboard>
15:10:36.326 Mac A usernoted Presenting notification take-failed-<address>
15:10:36.377 Mac A app Auto-reconnect: <keyboard> held by <Mac B>; leaving it
15:10:37.754 Mac B bluetoothd ACL disconnected: <address>, result 0, reason 10722
15:10:37.759 Mac B bluetoothd Device <address> is unpaired and now fully disconnected, resetting it
The next take, about 35 seconds after the release finished, reached the keyboard but pairing failed. The Macs' retries then logged Pairing failed for <address>: 2 and later : 4, until the keyboard was power-cycled.
All four releases over 2 seconds that I could time failed about 2 seconds after they started:
| Release started | Failure notification | Release finished | Duration |
|---|---|---|---|
| 15:09:59.037 | 15:10:01.177 | not captured | over 2s |
| 15:10:34.275 | 15:10:36.326 | 15:10:37.754 | 3.5s |
| 15:16:38.126 | 15:16:40.194 | 15:16:49.869 | 11.7s |
| 15:16:44.122 (second Take during the release above) | 15:16:46.181 | 15:16:49.869 | same release |
Releases that finished within the limit worked: Mac B in 1.1s, and Mac A in 0.8s and 1.6s.
Likely cause
Reading the code at 4cb56e9 (the same logic is in the v2.30.1 tag):
- The holder answers
UNREGISTER_ONEthroughreplyOnceDisconnected, which useswaitForLocalDisconnect. That polls 5 times at 0.5s intervals, so it reports failure after about 2 seconds, even though-removeis still completing in the Bluetooth daemon. - The taker lands in the
.failure(let err)arm oftakePeripheralFromPeer, shows the notification and arms theHOLDS_ONE-gated watcher. - The watcher's first
HOLDS_ONEarrives while the holder is still connected, so it gets a yes, andreclaimIfPeerIsFreedisarms itself. Nothing is left watching when the release completes moments later.
From reading the code (not observed), continueSendHandoff uses the same helper, so a slow release during Send should also report "didn't disconnect" and never send CONNECT_ONE, while the release still completes.
Why the release is sometimes slow
I don't have a confirmed cause. In the 11.7s release, bluetoothd on Mac B was running repeated HID report exchanges (getReportWithKeyhole, report ID 0x45) and logged Unplugging virtual cable only after they ended. In the 1.1s release those exchanges had already stopped. The 3.5s release fits less cleanly: it logged Unplugging virtual cable ... failed with status 118, then removed the link key 2 seconds later. This is a small sample, and I only saw releases over 2 seconds on the macOS 26 Mac.
Possible fix (untested)
- Extend the wait in
waitForLocalDisconnectto about 15 seconds.releaseBodyTimeouton the taker is already 20 seconds, so that should fit for Take. The longest release I saw was 11.7 seconds. - Don't let a
HOLDS_ONEyes disarm the watcher straight after a failed release, for example by keeping it armed for a grace period while the peer is still mid-release.
Other things I noticed
- Retries stopped: after a stranded keyboard's failures, the last retry I could see on Mac B was around 15:12:16, within the 10-minute window. I then power-cycled the keyboard and it did not reconnect until I clicked Take manually at 15:15:37. I don't know why, and it may be a separate problem.
- README setup step: "each peripheral has to be paired to both Macs" caused trouble for me. Pairing the keyboard with the second Mac made the first show "This Mac can no longer connect to ... Forget this device and pair it again." Pairing with one Mac, registering it, then using Sync and Take worked, which suggests the step may be unnecessary.
I can provide fuller logs if they would help.
Activity
MegaManSec commentedon Nov 4, 2026
Confirmed, thanks. #160 waits up to 15s for the release and keeps the taking Mac watching while it finishes; #162 fixes the README step.
For "retries stopped", could you share both Macs' logs from 15:10–15:16?