Skip to content

ble: record why the stale-pairing clear stays Android-only - #1791

Merged
ryanbr merged 3 commits into
mainfrom
docs/stale-bond-parity
Sep 1, 2026
Merged

ryanbr merged 3 commits into
mainfrom
docs/stale-bond-parity

Conversation

@ryanbr

@ryanbr ryanbr commented Sep 1, 2026 •

Copy link
Copy Markdown
Owner

You asked for a mirror of #1790. A mirror is not safely buildable, and this PR records why rather than faking one — with a correction to a claim I made in #1790's merge body.

What I got wrong

#1790 said "Android-only and permanently so: CoreBluetooth exposes no bond-removal API." That is true, and it is not the whole blocker. Stated that way it reads as settled, and it invites the next reader to go hunting for a way round.

The real obstacle is identity, not API

macOS does have a removal call — IOBluetooth, a separate classic-Bluetooth framework. It addresses devices by MAC, and CoreBluetooth never exposes one: a peripheral's identity there is CBPeripheral.identifier, a per-app UUID that is deliberately not the hardware address. I checked, and nothing in BLEManager ever holds a MAC.

That leaves exactly one bridge: enumerate IOBluetoothDevice.pairedDevices() and match by name. Deleting a pairing on a name match removes the wrong device the moment a user has two straps or has renamed one — which is precisely the mistake shouldRemoveStaleBond's guards exist to prevent. Closing the gap that way would be worse than leaving it open.

iOS has no route at all. Forgetting a pairing is a Settings → Bluetooth action reserved to the user by design.

Why this is worth writing down

Apple is not unaware of the condition. didFailToConnect already handles CBError.peerRemovedPairingInformation and publishes the same forget-and-re-pair guide, naming the strap:

Your strap's Bluetooth pairing was reset - usually by a WHOOP firmware update, or the official WHOOP app…
2. Open System Settings → Bluetooth and Forget "WHOOP MG" if it's listed.

So both platforms detect it, both give the same advice, and only Android can take the step. A future parity audit finding this one-sided should read a decision, not an oversight — the same treatment ExplicitBond and BondStateTrace already get.

Notes only, one on each side, cross-referencing each other. 638 BLE tests green, doc lint clean, swiftc -parse clean.

One divergence I did NOT change

Apple's stale-bond branch sets the guide and returns — it schedules nothing. Android's schedules a reconnect and keeps trying, which is how its failure counter climbs to five at all.

That means on Apple, a user who clears the pairing in System Settings while the app is open is not picked up until they reconnect by hand. It may well be deliberate — not hammering a strap whose bond is known-stale is defensible — and the comment above it cites #1413's standing-connect handoff for the other failure paths specifically. I am not changing Apple's reconnect behaviour on my reading of it; flagging it for yours.


Re-review: I made the same mistake inside the correction

The first version of these notes said macOS "does have" a bond-removal call in IOBluetooth. I cannot verify that from this repository. Asserting an Apple SDK capability from recollection is precisely the move this PR exists to correct — and I did it in the correction.

It was also unnecessary. The identity blocker makes the API question moot: even if IOBluetooth's removal works on a BLE pairing, it addresses devices by MAC and Apple's side never has one, so the question never arises. That argument is fully checkable from inside the repo, and it is the stronger one.

The note also had a citation available that I had not used. BLEManager.epitaphLine's own doc already says the peripheral UUID is "per-install, not the hardware address" — noop established this before I did, so the note now points at it rather than re-deriving it. I re-grepped BLEManager unfiltered to be sure nothing MAC-shaped had slipped past my earlier filtered search; the only two hits are that doc line and my own comment.

The closing line lost "if a future macOS SDK exposes the hardware address" for the same reason, and now turns on CoreBluetooth — the framework whose behaviour actually governs this.

#1790 said "Android-only and permanently so: CoreBluetooth exposes no
bond-removal API." That is true and it is not the whole blocker, and stating it
that way invites the next reader to go looking for a way round.

The real obstacle is IDENTITY. macOS DOES have a removal call, in IOBluetooth -
a separate classic-Bluetooth framework - but it addresses devices by MAC, and
CoreBluetooth never exposes one. A peripheral's identity there is
CBPeripheral.identifier, a per-app UUID that deliberately is not the hardware
address. The only bridge left is matching IOBluetoothDevice.pairedDevices() by
NAME, and deleting a pairing on a name match removes the wrong device the moment
a user has two straps or has renamed one. That is the exact mistake
shouldRemoveStaleBond's guards exist to prevent, so closing the gap that way
would be worse than leaving it open. iOS has no route at all.

Worth writing down because Apple is not unaware of the condition: didFailToConnect
already handles CBError.peerRemovedPairingInformation and publishes the same
forget-and-re-pair guide, naming the strap. Both platforms detect it, both give
the same advice, and only one can take the step itself. A future audit finding
this one-sided should read a decision rather than an oversight.

Notes only, on both sides, cross-referencing each other. 638 BLE tests green.
Re-review of my own correction. The note said macOS "does have" a bond-removal
call in IOBluetooth. I cannot verify that from this repository, and asserting an
Apple SDK capability on recollection is the same move the note exists to correct
- inside the correction.

It is also unnecessary. The identity blocker makes the API question moot: even
if IOBluetooth's removal works on a BLE pairing, it addresses devices by MAC and
Apple's side never has one, so the question never arises. That argument is
fully checkable from inside the repo, and it is the stronger one.

It also had a citation already: BLEManager.epitaphLine's own doc says the
peripheral UUID is "per-install, not the hardware address". noop established
this before I did; the note now points at it rather than re-deriving it. Grepped
BLEManager unfiltered to be sure nothing MAC-shaped had slipped past the earlier
filtered search - the only two hits are that doc line and my own comment.

The closing line loses "if a future macOS SDK exposes the hardware address" for
the same reason, and now turns on CoreBluetooth, which is the framework whose
behaviour actually governs this.

638 BLE tests green; doc lint and swiftc -parse clean.
Third pass. Having removed "macOS does have a removal call" as an SDK claim I
cannot check from here, I had left "iOS has no route at all ... reserved to the
user, by design" standing two lines above it. That is the same kind of
assertion, and the second half is worse: it is a claim about Apple's INTENT, not
just its API.

The note does not need either. The identity blocker covers iOS too - no MAC, so
nothing to address - and the app's own guide already tells the user to do it in
Settings, which is the observable fact rather than a story about why.

A note whose subject is "do not assert what you cannot check" should read that
way throughout, including the parts that happen to be conventional wisdom.

638 BLE tests green; doc lint and swiftc -parse clean.
@ryanbr
ryanbr merged commit 1a9d6c4 into main Sep 1, 2026
4 checks passed
@ryanbr
ryanbr deleted the docs/stale-bond-parity branch September 1, 2026 08:01
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.

1 participant