El calendario de Microsoft 365 no llega al Mac: cómo despertar exchangesyncd

El calendario de Microsoft 365 no llega al Mac: cómo despertar exchangesyncd
ContenidoContents

Me ha pasado más veces de las que me gusta admitir. Llega una invitación de trabajo, la acepto en el iPhone, el móvil la pinta en un segundo… y en el Mac mini, el que tengo delante todo el día, el hueco sigue vacío.

El correo de Microsoft 365 llega. iCloud sincroniza. Calendario no enseña ningún error. Cmd+R a veces no basta. Y al cabo de un rato —o de un día— te das cuenta de que el Mac no se ha enterado de la reunión.

No es tu cuenta. Es un clásico de Calendario en macOS con Exchange / Microsoft 365.

Qué está pasando

En el iPhone, Calendario habla con Exchange por ActiveSync y el empujón (push) funciona. En el Mac, Calendario también usa Exchange, pero el proceso que mantiene viva esa conversación se llama exchangesyncd. Hace un ping al servidor, y en macOS ese ping se muere en silencio. Sin aviso, sin triángulo amarillo, sin pedir la contraseña otra vez.

Mientras tanto el correo sigue llegando (otro canal) e iCloud sigue sincronizando (otro canal). Solo se queda dormido el calendario de trabajo.

En los foros de Apple el arreglo más repetido —desde Monterey hasta Sequoia y Tahoe— es forzar la salida de exchangesyncd. El proceso se relanza solo y el calendario vuelve a tirar… hasta que se vuelve a colgar. Hay hilos con más de cien votos. Hay gente con un cron cada hora haciendo exactamente eso. No es un invento mío.

Cómo saber si es esto (y no otra cosa)

Abre Calendario, barra lateral, y confirma que el calendario de Exchange está marcado. Si está, el siguiente paso es mirar cuándo sincronizó por última vez. En macOS reciente la base vive aquí:

~/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb

Lista las cuentas y su último sync (la fecha es Core Foundation: segundos desde el 1 de enero de 2001):

 1python3 - <<'PY'
 2import sqlite3, datetime, os
 3db = os.path.expanduser(
 4    "~/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb"
 5)
 6c = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
 7APPLE = 978307200
 8for name, start, end in c.execute(
 9    "SELECT name, last_sync_start, last_sync_end FROM Store"
10):
11    def fmt(v):
12        return datetime.datetime.fromtimestamp(v + APPLE) if v else "—"
13    print(f"{name:30} {fmt(start)}  →  {fmt(end)}")
14PY

En mi caso iCloud había sincronizado esa misma mañana. El store de Exchange llevaba más de un día parado, y el último intento había durado 62 milisegundos: un latido vacío, no un volcado. Ahí está la pista. Si Exchange está en verde y aun así last_sync_end es de ayer, exchangesyncd se ha dormido.

El arreglo de treinta segundos

Cierra Calendario si quieres (no es obligatorio) y en Terminal:

1killall exchangesyncd

macOS lo vuelve a lanzar al momento. Abre Calendario y Vista → Actualizar calendarios (o Cmd+R). En unos segundos deberían aparecer las reuniones que el iPhone ya tenía.

Si no aparece nada, el siguiente escalón es desactivar y volver a activar Calendarios en Ajustes del Sistema → Cuentas de internet → la cuenta Exchange. Eso fuerza un resync completo. Es más brusco; yo empiezo por matar el proceso.

Dejarlo en piloto automático

Como el cuelgue vuelve, no sirve de mucho acordarse de killall cada vez que falta una reunión. En el Mini dejé un LaunchAgent que, cada 15 minutos, mira si el store de Exchange lleva más de 20 minutos sin sincronizar. Solo entonces relanza exchangesyncd.

Primero averigua el nombre exacto del store (el de la consulta anterior: en mi máquina es el de la cuenta de trabajo). Ponlo en STORE_NAME.

~/bin/refresh-o365-calendar.sh:

 1#!/bin/zsh
 2set -euo pipefail
 3
 4CALDB="$HOME/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb"
 5LOG="$HOME/Library/Logs/refresh-o365-calendar.log"
 6STALE_SECS=1200
 7STORE_NAME="Nombre de tu cuenta Exchange"   # el de la consulta SQL
 8
 9ts() { date '+%Y-%m-%d %H:%M:%S'; }
10
11age=$(python3 - "$CALDB" "$STORE_NAME" <<'PY'
12import sqlite3, sys, time
13db, name = sys.argv[1], sys.argv[2]
14c = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
15row = c.execute("SELECT last_sync_end FROM Store WHERE name=?", (name,)).fetchone()
16if not row or row[0] is None:
17    sys.exit(2)
18print(int((time.time() - 978307200) - float(row[0])))
19PY
20) || {
21  echo "[$(ts)] no last_sync for $STORE_NAME, bouncing exchangesyncd" >>"$LOG"
22  killall exchangesyncd 2>/dev/null || true
23  exit 0
24}
25
26if (( age > STALE_SECS )); then
27  echo "[$(ts)] $STORE_NAME stale ${age}s, bouncing exchangesyncd" >>"$LOG"
28  killall exchangesyncd 2>/dev/null || true
29  if pgrep -x Calendar >/dev/null; then
30    osascript -e 'tell application "Calendar" to reload calendars' >/dev/null 2>&1 || true
31  fi
32fi

Hazlo ejecutable (chmod +x) y cárgalo con un plist en ~/Library/LaunchAgents/. Lo importante: StartInterval de 900 segundos y RunAtLoad.

 1<?xml version="1.0" encoding="UTF-8"?>
 2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
 3  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 4<plist version="1.0">
 5<dict>
 6  <key>Label</key>
 7  <string>com.example.refresh-o365-calendar</string>
 8  <key>ProgramArguments</key>
 9  <array>
10    <string>/bin/zsh</string>
11    <string>/Users/TU_USUARIO/bin/refresh-o365-calendar.sh</string>
12  </array>
13  <key>StartInterval</key>
14  <integer>900</integer>
15  <key>RunAtLoad</key>
16  <true/>
17</dict>
18</plist>

Cárgalo:

1launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.example.refresh-o365-calendar.plist

No relanza el proceso si acaba de sincronizar. Si Calendario está abierto, le pide un refresh para que la UI no se quede con el hueco vacío.

Lo que no es

No es que Microsoft 365 “no funcione en Mac”. Outlook para Mac, la web y el iPhone suelen estar al día. El que falla es Calendario.app hablando por Exchange. Tampoco es (casi nunca) un problema de contraseña: la cuenta sigue autenticada y el correo sigue entrando.

Si vives en Outlook, este artículo no te hace falta. Si quieres un solo Calendario nativo en el Mac, con iCloud, el de trabajo y el de los festivos, entonces sí: exchangesyncd es el que hay que vigilar.

Recapitulando

  1. Comprueba last_sync_end del store Exchange.
  2. Si está parado: killall exchangesyncd y Cmd+R.
  3. Si te pasa a menudo: el LaunchAgent de arriba.

No es elegante. Es el mismo apaño que lleva años circulando en los foros de Apple, solo que aquí no hace falta acordarse.

CompartirShare