The pairing zombie-connect check added by #133 (in next-dev) has the same defect fixed on the dev backport in #141 at d70d7b8 (5a32565 was a first attempt that only narrowed the misfire; d70d7b8 replaces it) — see #141 (comment) for the full log analysis.
Summary. In centralManager(_:didDiscover:…), a pairable pod heard at state=.connecting with no active watchdog is treated as a zombie connect and cancelled. But discoverPods pre-connects any already-known disconnected pod before starting the scan, and timedConnect only arms the watchdog when shouldUseEagerConnect is true (iPhone 16/17e). On any other phone that pre-connect is unsupervised, the scan hears the pod ~0.8s later at .connecting, cancels it, the teardown timer reissues an equally unsupervised connect, and the next advert cancels that — pairing loops at the advert interval (~1.6s) and never completes. A peripheral keeps advertising until the link is established and CoreBluetooth reports .connecting from the moment connect() is called, so an advert in the first second or two is the normal case, not evidence of a stale connect.
Reproduced deterministically on an iPhone SE pairing an Omnipod 5 that was already a known device (Trio, two captures: 62 and 104 consecutive cycles). Pairing a pod that is not yet known takes the state=0 path and merely races the same ~1.5s window, which is why earlier testing on Loop next-dev and iAPS happened to pass.
Fix to port. d70d7b8 on backport/eager-connect-dev: remove the zombie branch from didDiscover (connect only when .disconnected), and close the source of orphaned connects instead — endPodDiscovery cancels any .connecting peripheral (the old !autoConnectIDs exemption leaked them, since discovery adds the pod to autoConnectIDs before ending), and discoverPods cancels any unsupervised .connecting peripheral before issuing its own. Net −37 lines; the didDiscover/endPodDiscovery/discoverPods code is the same in both branches so it should cherry-pick cleanly.
The pairing zombie-connect check added by #133 (in
next-dev) has the same defect fixed on thedevbackport in #141 at d70d7b8 (5a32565 was a first attempt that only narrowed the misfire; d70d7b8 replaces it) — see #141 (comment) for the full log analysis.Summary. In
centralManager(_:didDiscover:…), a pairable pod heard atstate=.connectingwith no active watchdog is treated as a zombie connect and cancelled. ButdiscoverPodspre-connects any already-known disconnected pod before starting the scan, andtimedConnectonly arms the watchdog whenshouldUseEagerConnectis true (iPhone 16/17e). On any other phone that pre-connect is unsupervised, the scan hears the pod ~0.8s later at.connecting, cancels it, the teardown timer reissues an equally unsupervised connect, and the next advert cancels that — pairing loops at the advert interval (~1.6s) and never completes. A peripheral keeps advertising until the link is established and CoreBluetooth reports.connectingfrom the momentconnect()is called, so an advert in the first second or two is the normal case, not evidence of a stale connect.Reproduced deterministically on an iPhone SE pairing an Omnipod 5 that was already a known device (Trio, two captures: 62 and 104 consecutive cycles). Pairing a pod that is not yet known takes the
state=0path and merely races the same ~1.5s window, which is why earlier testing on Loop next-dev and iAPS happened to pass.Fix to port. d70d7b8 on
backport/eager-connect-dev: remove the zombie branch fromdidDiscover(connect only when.disconnected), and close the source of orphaned connects instead —endPodDiscoverycancels any.connectingperipheral (the old!autoConnectIDsexemption leaked them, since discovery adds the pod toautoConnectIDsbefore ending), anddiscoverPodscancels any unsupervised.connectingperipheral before issuing its own. Net −37 lines; thedidDiscover/endPodDiscovery/discoverPodscode is the same in both branches so it should cherry-pick cleanly.