mi apple watch se emparejó bien. luego lo olvidó.
actualización, 3 de octubre: resultó que se arregló actualizando el iphone a ios 27.2, no con nada en el reloj. el seguimiento tiene la evidencia y sus límites: el reloj no estaba roto. mi iphone iba atrasado. lo que sigue es la investigación tal como la escribí en su momento.
un informe de un apple watch ultra 2 que desapareció de xcode 27 y no volvió. este no está resuelto. lo que tengo es una descripción mucho más estrecha del fallo que "no conecta", construida con logs del mac y del reloj, un experimento que cambió mi teoría y un callejón sin salida con un host de emparejamiento independiente. está reportado a apple como FB24924229.
resumen
- el reloj pasó a
unavailableendevicectly luego desapareció. volver a emparejarlo desde device hub funciona siempre: srp pair-setup m1–m6,setupManualPairing succeeded. - ~30 ms después el canal se cierra. un iphone hace exactamente lo mismo y reconecta 1,4 s después con
verifyManualPairing. el reloj nunca. - los relojes se emparejan hacia el mac, y el mac solo escucha mientras la hoja de emparejamiento está abierta. cuando lo mantuve escuchando, el reloj volvió 23 s después, pero pidió un emparejamiento nuevo en vez de verificar el que acababa de terminar. entra en bucle.
- así que el lado del mac funciona. el reloj no conserva, o no usa, su mitad del emparejamiento. ese código vive en watchos, que no puedo cambiar.
el síntoma
$ xcrun devicectl list devices
iPhone (2) available (paired) iPhone 15 Pro Max
iPad available (paired) iPad Pro 12.9-inch
(no watch)
$ xcrun devicectl manage pair --device <watch>
The specified device was not found (1000)
device hub mostraba "no disponible por ahora — debe estar cerca". instalar desde la app watch del iphone avanzaba hasta la mitad y se atascaba. antes descarté bluetooth, wi-fi, el cable, el modo desarrollador y la propia app. la lista completa está en what-i-tried.md.
1. el emparejamiento funciona y luego el canal se cierra
remotepairingd en el mac muestra un emparejamiento completo y exitoso, seguido de un cierre:
22:21:41.314 PairSetup server done -- client authenticated
22:21:41.314 tcp-79: Pairing session of kind setupManualPairing succeeded
22:21:41.344 tcp-79: received error reading message <-- +30 ms
22:21:41.345 tcp-79: authenticated -> invalidated
22:21:41.663 DeviceHub: Beaconing pairing session explicitly ended by client
mi primer informe llamó a ese cierre de 30 ms el bug. no lo era.
2. un control: el iphone hace lo mismo
tras restablecer ubicación y privacidad, el iphone también tuvo que volver a emparejarse, un minuto antes que el reloj, en el mismo mac:
| setup exitoso | canal cerrado | reconexión | |
|---|---|---|---|
| iphone | 10:54:22.173 | +36 ms | +1,4 s verifyManualPairing → available |
| reloj | 10:55:08.951 | +29 ms | ninguna |
el cierre es normal: termina el setup y el dispositivo reconecta con una sesión verificada. lo que falta es la reconexión del reloj. reescribí el repo y el reporte a apple para decir eso. un borrador anterior usó la reconexión de un ipad como control, que no era un setup nuevo, así que tampoco era una comparación justa.
3. los relojes se emparejan hacia el mac
el mac encuentra un iphone buscando _remotepairing._tcp. el reloj nunca anunció eso, y yo tenía "el reloj no se anuncia" como hipótesis principal. leer un log stream en vivo durante el emparejamiento mostró que el flujo es al revés para un reloj:
22:21:16 remotepairingd: Started listening for network pairing <-- sheet opened
22:21:41 setupManualPairing succeeded
22:21:41 DeviceHub: Beaconing pairing session explicitly ended <-- sheet closes itself
el reloj se conecta al listener _remotepairing-pairable-host._tcp del mac, y ese listener existe solo mientras la hoja emparejar dispositivo cercano… de device hub está abierta. la hoja se cierra sola ~300–400 ms después del setup. así que probablemente el reloj no se anuncia por diseño, y apareció una teoría nueva: quizá el reloj intenta reconectar, pero ya no queda nada a lo que conectarse.
4. mantener al mac escuchando
reabrir la hoja a mano dejaba un hueco de 15 s y no volvía nada. para cerrar el hueco escribí watch-pair-keeper.sh: sigue remotepairingd y, en cuanto device hub suelta el listener, vuelve a pulsar emparejar dispositivo cercano… mediante system events, y luego reporta si el reloj verificó. antes de usarlo en vivo le pasé un log capturado para comprobar los tiempos.
22:53:00.613 setupManualPairing succeeded (watch) closed at +305 ms
22:53:01.533 mac listening again gap: 615 ms
22:53:23.502 Network pairing peers updated. Total count: 1 <-- the watch is back
22:53:23.558 PairingData(startNewSession: true, kind: setupManualPairing)
22:53:23.559 DeviceHub: Presenting pairing challenge <-- a new code
22:53:33.642 setupManualPairing succeeded (watch) closed again at +286 ms
eso refutó mi teoría y dio una respuesta mejor. el reloj sí reconecta. pero pide un pair-setup nuevo, no un pair-verify, como si hubiera olvidado el emparejamiento que terminó 23 segundos antes. el mac guardó el emparejamiento y estaba escuchando; el reloj no usó su parte. setup, cierre, setup, cierre.
un falso positivo que vale la pena mencionar: el script marcó un verifyManualPairing a las 22:53:59. era el ipad. el script solo cuenta un verify cuando la siguiente línea autenticada lleva el prefijo del udid del reloj, y por eso no reportó éxito.
5. los logs propios del reloj
un sysdiagnose del reloj (corona + botón lateral, y luego por el iphone) mostró a remotepairingdeviced iniciando el emparejamiento con normalidad: resuelve el anuncio del mac, conecta por wi-fi, lockdownShouldDisableDevicePairing: NO, "manual pairing in progress". luego el archivo no tiene líneas de ningún proceso entre las 22:21:34.1 y las 22:21:48, así que el lado del reloj del cierre de las 22:21:41 no está. (el primer archivo que salió era el del iphone. el nombre dice iPhone-OS o Watch-OS; compruébalo antes de ponerte a analizar.)
6. un host independiente
si el lado del mac no se puede cambiar, quizá se pueda reemplazar. pymobiledevice3 11.19.1 trae remote pair-host, que anuncia un host emparejable y acepta un emparejamiento iniciado por el dispositivo. el reloj lo listó en desarrollador ▸ macs emparejados ▸ otros dispositivos, pidió el código del reloj y luego nunca abrió una conexión tcp.
al host de apple se llega por ipv6 link-local; pymobiledevice3 se enlaza a 0.0.0.0 y solo publica un registro a. así que lo envolví (pair_host_dualstack.py) para escuchar en ambas familias y anunciar también el registro aaaa. mismo resultado: ninguna conexión, por ninguna de las dos. device hub además ejecuta una sesión "beaconing" que pymobiledevice3 no tiene. si el reloj la necesita, todavía no lo sé.
dónde está
- la falla está en el reloj: tras un setup exitoso vuelve pidiendo uno nuevo.
- quitar el mac en el reloj (desarrollador ▸ desvincular este dispositivo) y emparejar de nuevo no cambia nada.
- nada del lado del mac lo arregló. el único paso sin probar es borrar el reloj.
- la evidencia del bucle de emparejamiento fue a apple como seguimiento de FB24924229.
errores míos en el camino
- llamar bug al cierre de 30 ms. un control con el iphone mostró que es normal. corregido en el repo y en el reporte.
- "el reloj no se anuncia" como hipótesis principal. los relojes se emparejan hacia el mac. tachada.
- "el mac deja de escuchar demasiado pronto" como arreglo. el experimento que armé para probarlo lo refutó, y así apareció el hallazgo real.
lo que me llevo
- un dispositivo de control vale más que otra teoría. que el iphone hiciera el mismo cierre en el mismo minuto replanteó todo el problema.
- lee quién se conecta a quién. asumí que el mac encuentra al reloj. es al revés, y eso cambió qué ausencia importaba.
- un experimento que no arregla algo puede ser igual el resultado. mantener el listener abierto no arregló el emparejamiento; expuso el bucle.
- "no se puede arreglar desde aquí" debe venir con evidencia. el host de apple, mi arreglo del listener y un host independiente, todo con marcas de tiempo.
enlaces
- kisnner26/watchos27-pairing-bug: logs depurados, cronología, herramientas y el texto del asistente de feedback
- doronz88/pymobiledevice3, el host independiente del paso 6