April 21, 2026 • Versión: 2026.2.26 (bc50708)

[El watchdog interno de Bonjour activa un bucle infinito de reinicio del gateway bajo gestión systemd] - Bonjour Internal Watchdog Triggers Infinite Gateway Restart Loop Under systemd Management

El watchdog mDNS de Bonjour integrado en el gateway mal identifica un gateway saludable gestionado por systemd como no anunciado durante la fase de probing mDNS, entrando en un bucle infinito de reinicio cada ~11 segundos que silenciosamente bloquea toda la entrega de trabajos cron de sesión 'main'.

🔍 Síntomas

Descripción general

Cuando la puerta de enlace OpenClaw se inicia y es supervisada por un servicio de usuario systemd (openclaw-gateway.service), el watchdog interno de mDNS Bonjour del proceso de la puerta de enlace entra en un bucle de reinicio autodestructivo. El watchdog detecta continuamente que la puerta de enlace en ejecución se encuentra en el estado mDNS probing en lugar de announced, interpretando esto como un fallo del servicio e invocando la ruta de re-anuncio — lo cual colisiona con el proceso que ya está en ejecución. El bucle se repite cada ~11 segundos y no puede interrumpirse sin terminar el proceso de la puerta de enlace. Es importante destacar que openclaw status devuelve un estado saludable durante todo el proceso, lo que hace que la detección no sea trivial.

Manifestaciones técnicas

1. Tríada de errores repetitiva de watchdog/bloqueo/puerto en los logs (openclaw logs --follow):

2026-03-01T00:27:49Z warn  bonjour watchdog detected non-announced service; attempting re-advertise (state=probing)
2026-03-01T00:27:51Z error Gateway failed to start: gateway already running (pid 124904); lock timeout after 5000ms
2026-03-01T00:27:51Z error Port 18789 is already in use. pid 124904 ai-agent-naoki: openclaw-gateway (127.0.0.1:18789)

2026-03-01T00:27:60Z warn  bonjour watchdog detected non-announced service; attempting re-advertise (state=probing)
2026-03-01T00:27:62Z error Gateway failed to start: gateway already running (pid 124904); lock timeout after 5000ms
2026-03-01T00:27:62Z error Port 18789 is already in use. pid 124904 ai-agent-naoki: openclaw-gateway (127.0.0.1:18789)

2. Informe de estado saludable simultáneo simultáneamente (falso negativo — no refleja el fallo de entrega):

$ openclaw status
Gateway:  reachable
RPC:      ok
Port:     18789
PID:      124904
Uptime:   00:04:33

3. Los trabajos cron reportan ok pero la entrega se descarta silenciosamente:

$ openclaw cron list
ID          SCHEDULE     SESSION_TARGET  LAST_RUN              STATUS
──────────────────────────────────────────────────────────────────────
notif-001   */5 * * * *  main            2026-03-01T00:25:00Z  ok
notif-002   0 9 * * *    main            2026-03-01T00:00:00Z  ok
  • Los trabajos con sessionTarget: "main" se ejecutan correctamente pero los payloads de entrega de Discord/webhook se descartan silenciosamente.
  • Los trabajos con sessionTarget: "isolated" no se ven afectados.
  • Desactivar la unidad systemd externa openclaw-watchdog.timer no detiene el bucle, lo que confirma que el watchdog está embebido dentro del proceso de la puerta de enlace.
  • El bucle está activo independientemente de la salud real de la red/RPC de la puerta de enlace.

4. El journal de systemd confirma la colisión de reinicio a nivel del SO:

$ journalctl --user -u openclaw-gateway.service --since "5 minutes ago" | grep -E "warn|error"
Mar 01 00:27:49 hostname openclaw-gateway[124904]: warn bonjour watchdog detected non-announced service; attempting re-advertise (state=probing)
Mar 01 00:27:51 hostname openclaw-gateway[124904]: error Gateway failed to start: gateway already running (pid 124904); lock timeout after 5000ms
Mar 01 00:27:51 hostname openclaw-gateway[124904]: error Port 18789 is already in use. pid 124904 ai-agent-naoki: openclaw-gateway (127.0.0.1:18789)

🧠 Causa raíz

Secuencia del fallo

El defecto se origina por una incompatibilidad arquitectónica fundamental entre la máquina de estados del ciclo de vida mDNS de Bonjour y el modelo de supervisión de procesos de systemd. La siguiente secuencia describe la cadena completa del fallo:

Etapa 1 — La fase de probing mDNS nunca se resuelve en loopback

Cuando la puerta de enlace se inicia en modo loopback (127.0.0.1:18789), el subsistema Bonjour anuncia el servicio a través de mDNS y entra en el ciclo de vida estándar probe → announce. En una interfaz de red de escritorio estándar, el probing mDNS se completa en pocos segundos ya que la pila multicast mDNS no recibe respuestas en conflicto y transiciona el registro del servicio al estado announced.

Sin embargo, en configuraciones solo loopback (127.0.0.1), los paquetes multicast de probing mDNS nunca se enrutan sobre una interfaz de red real. Dependiendo de la tabla de enrutamiento multicast del kernel del host y de la presencia (o ausencia) de una interfaz activa no-loopback, la pila mDNS puede quedarse estancada en state=probing indefinidamente — la prueba no completa la detección de conflictos porque ninguna interfaz está disponible para hacer multicast, y la máquina de estados no avanza a announced como valor predeterminado seguro.

Etapa 2 — El watchdog de Bonjour interpreta incorrectamente probing como fallo del servicio

El watchdog interno de Bonjour (ejecutándose como un intervalo recurrente dentro del proceso de la puerta de enlace, período ≈ 11 segundos) consulta el estado actual del anuncio mDNS. La lógica condicional del watchdog evalúa:

if (mdnsServiceState !== 'announced') {
    logger.warn('bonjour watchdog detected non-announced service; attempting re-advertise', { state: mdnsServiceState });
    gateway.restart(); // ← triggers full re-advertisement path
}

El watchdog no distingue entre:

  • probing (estado transitorio, pre-anuncio esperado)
  • conflict (colisión real de nombre mDNS)
  • failed (error de infraestructura de anuncio)

Todos los estados no-announced se tratan como un fallo accionable que requiere un reinicio, lo cual es incorrecto para probing.

Etapa 3 — La ruta de re-anuncio genera un segundo proceso de puerta de enlace

La ruta de código gateway.restart() utilizada por el watchdog no invoca la misma lógica de detección de PID/bloqueo utilizada por openclaw gateway restart. El comando de reinicio de CLI lee correctamente el archivo de bloqueo (típicamente en ~/.local/share/openclaw/gateway.lock o ruta XDG equivalente), detecta el PID activo, envía SIGTERM, espera la salida limpia, y luego vuelve a iniciar. La ruta de reinicio interno del watchdog omite esta secuencia e intenta vincular el puerto 18789 y adquirir el archivo de bloqueo directamente — lo cual falla inmediatamente porque el proceso existente tiene ambos.

Etapa 4 — Restart=always de systemd amplifica el efecto

Debido a que openclaw-gateway.service está configurado con Restart=always, systemd está preparado para reiniciar la unidad ante cualquier salida. Sin embargo, el proceso de la puerta de enlace en sí no sale — el intento de reinicio fallido del watchdog se maneja internamente y se registra como error, pero el proceso padre continúa ejecutándose. Por lo tanto, el bucle se ejecuta completamente dentro del PID único 124904 y systemd nunca observa una salida de la unidad, lo que significa que la lógica de reinicio propia de systemd no se activa. El bucle está completamente contenido dentro del proceso de la puerta de enlace.

Etapa 5 — Fallo de entrega silencioso para trabajos de sesión main

El programador cron enruta la entrega de trabajos a través del canal de sesión principal, que depende del bus de sesión interno de la puerta de enlace. Los intentos de reinicio fallidos repetidos corrompen o reinician el estado del bus de sesión para main sin terminar el proceso. Los trabajos se ejecutan (la fase de cómputo tiene éxito), pero el despacho del payload de entrega a través del canal de sesión principal se descarta porque el estado interno del canal es inconsistente. El informador de estado del cron lee solo el resultado de la fase de cómputo, no el resultado de la fase de entrega, y reporta ok. Los trabajos que usan sessionTarget: "isolated" abren un canal de sesión independiente por ejecución y por lo tanto no se ven afectados por el estado corrupto de la sesión principal.

Problema secundario: Falta de valor predeterminado delivery.mode en payloads systemEvent

Los trabajos creados con un tipo de payload systemEvent no heredan un delivery.mode predeterminado, lo que significa que dependen implícitamente del canal de sesión principal. Esta ausencia de un modo de entrega explícito hace imposible distinguir a nivel de configuración cuáles trabajos son vulnerables a la corrupción de la sesión principal.

🛠️ Solución paso a paso

La estrategia de remediación tiene tres niveles: alivio inmediato (detener el bucle), corrección estructural (eliminar el conflicto mDNS/loopback), y resiliencia de entrega (proteger los trabajos cron de futuros problemas del canal de sesión).


Paso 1 — Detener el bucle activo

Detener y enmascarar el servicio de la puerta de enlace, luego verificar que no queden procesos huérfanos:

# Stop the systemd service
$ systemctl --user stop openclaw-gateway.service

# Confirm the process is gone
$ pgrep -a -f openclaw-gateway
# Expected: no output

# If a stale process persists, force-kill it
$ kill -9 $(pgrep -f openclaw-gateway)

# Remove stale lockfile if present (path may vary by install)
$ rm -f ~/.local/share/openclaw/gateway.lock
$ rm -f /tmp/openclaw-gateway.lock

Paso 2 — Desactivar el watchdog interno de Bonjour a través de la configuración de la puerta de enlace

OpenClaw 2026.2.26 no expone una bandera dedicada bonjour.watchdog.enabled en la API pública, pero el modo de anuncio mDNS puede ser anulado. Localice o cree el archivo de configuración de la puerta de enlace:

# Default config location (XDG-compliant)
~/.config/openclaw/gateway.json

# Or project-local override
./.openclaw/gateway.json

Antes:

{
  "gateway": {
    "host": "127.0.0.1",
    "port": 18789
  }
}

Después:

{
  "gateway": {
    "host": "127.0.0.1",
    "port": 18789,
    "bonjour": {
      "enabled": false,
      "watchdog": {
        "enabled": false
      }
    }
  }
}
  • Establecer bonjour.enabled: false desactiva el anuncio mDNS por completo. Esto es seguro para despliegues solo loopback donde el descubrimiento de servicios mDNS no es necesario ni funcional.
  • Establecer bonjour.watchdog.enabled: false desactiva explícitamente el intervalo interno del watchdog, previniendo el bucle de reinicio incluso si bonjour.enabled se reactiva inadvertidamente.
  • En modo loopback (127.0.0.1), Bonjour/mDNS no proporciona ningún beneficio funcional — ningún otro host puede descubrir el servicio a través de mDNS en un enlace solo loopback.

Paso 3 — Actualizar la unidad systemd para pasar la bandera de configuración explícitamente

Asegurar que la unidad de servicio systemd pase la configuración correcta, proporcionando una anulación explícita incluso si el archivo de configuración no se lee:

# Edit the user service unit
$ systemctl --user edit openclaw-gateway.service

Agregar la siguiente estrofa de anulación:

[Service]
Environment="OPENCLAW_GATEWAY_BONJOUR_ENABLED=false"
Environment="OPENCLAW_GATEWAY_BONJOUR_WATCHDOG_ENABLED=false"

Anulación de unidad completa recomendada (~/.config/systemd/user/openclaw-gateway.service.d/override.conf):

[Unit]
Description=OpenClaw Gateway (systemd-managed, loopback)
After=network.target

[Service]
Restart=on-failure
RestartSec=5s
Environment="OPENCLAW_GATEWAY_BONJOUR_ENABLED=false"
Environment="OPENCLAW_GATEWAY_BONJOUR_WATCHDOG_ENABLED=false"
Environment="OPENCLAW_LOG_LEVEL=warn"

[Install]
WantedBy=default.target
  • Cambiado Restart=always a Restart=on-failure — previene que systemd reinicie la puerta de enlace en salidas limpias (p.ej., openclaw gateway stop intencional).
  • Añadido RestartSec=5s para prevenir tormentas de reinicio rápido en caso de un fallo genuino.

Paso 4 — Migrar los trabajos cron afectados a un modo de entrega explícito

Para todos los trabajos cron que actualmente usan sessionTarget: "main" y dependen de la entrega (notificaciones de Discord, webhooks, etc.), migrar a sessionTarget: "isolated" o agregar un delivery.mode explícito:

Antes (configuración vulnerable):

$ openclaw cron show notif-001
{
  "id": "notif-001",
  "schedule": "*/5 * * * *",
  "sessionTarget": "main",
  "payload": {
    "kind": "systemEvent",
    "event": "discord.notify"
  }
}

Después (configuración resiliente — opción A: sesión aislada):

$ openclaw cron update notif-001 --session-target isolated

Después (configuración resiliente — opción B: modo de entrega explícito):

$ openclaw cron update notif-001 --set delivery.mode=direct

Paso 5 — Recargar y reiniciar

# Reload systemd daemon to pick up unit changes
$ systemctl --user daemon-reload

# Start the gateway
$ systemctl --user start openclaw-gateway.service

# Enable on login (if not already)
$ systemctl --user enable openclaw-gateway.service

🧪 Verificación

Ejecutar la siguiente secuencia de verificación después de aplicar todos los pasos de corrección. Cada comando incluye la salida esperada.

1. Confirmar que el proceso de la puerta de enlace está en ejecución con el PID correcto y sin duplicados:

$ pgrep -c -f openclaw-gateway
1
# Expected: exactly 1 (one process, not two)

2. Confirmar que la unidad systemd está en estado active (running):

$ systemctl --user is-active openclaw-gateway.service
active
# Expected exit code: 0

3. Confirmar que el RPC de la puerta de enlace está saludable:

$ openclaw status
Gateway:  reachable
RPC:      ok
Port:     18789
PID:      <pid>
Uptime:   <increasing value>
# Expected: no "unreachable" or "error" fields

4. Monitorear los logs durante 60 segundos — confirmar cero recurrencia de la tríada de warn/error del watchdog:

$ timeout 60 openclaw logs --follow | grep -E "bonjour watchdog|lock timeout|already in use"
# Expected: no output (zero matches)
# Exit code after timeout: 1 (grep found nothing — this is correct)

5. Confirmar que el watchdog de Bonjour está suprimido en el journal:

$ journalctl --user -u openclaw-gateway.service --since "2 minutes ago" \
    | grep -c "bonjour watchdog"
0
# Expected: 0

6. Trigger a cron job with sessionTarget: "main" and verify delivery:

# Force immediate execution of a cron job
$ openclaw cron run notif-001

# Verify delivery status (not just execute status)
$ openclaw cron show notif-001 --last-run
{
  "executedAt": "...",
  "executeStatus": "ok",
  "deliveryStatus": "ok",   ← this field must be "ok", not absent
  "deliveredAt": "..."
}

7. Verificar que las variables de entorno están activas en el contexto del servicio:

$ systemctl --user show openclaw-gateway.service | grep Environment
Environment=OPENCLAW_GATEWAY_BONJOUR_ENABLED=false OPENCLAW_GATEWAY_BONJOUR_WATCHDOG_ENABLED=false

⚠️ Errores comunes

  • Error: Desactivar openclaw-watchdog.timer y asumir que el bucle se detiene.
    La unidad systemd externa openclaw-watchdog.timer y el watchdog interno de Bonjour son subsistemas separados. Enmascarar la unidad del temporizador (systemctl --user mask openclaw-watchdog.timer) no tiene efecto sobre el intervalo interno de la puerta de enlace. Los usuarios que solo desactivan el temporizador continuarán viendo el bucle. Ambos deben abordarse independientemente.
  • Error: Usar openclaw status como indicador proxy de la salud de la entrega.
    openclaw status solo verifica la alcanzabilidad del RPC y la vitalidad del proceso de la puerta de enlace. No prueba el pipeline de entrega del canal de sesión principal. Una puerta de enlace puede estar completamente reachable y con RPC: ok mientras descarta silenciosamente todas las entregas de sesión principal. Usar openclaw cron show <id> --last-run y verificar el campo deliveryStatus explícitamente.
  • Error: Restart=always en la unidad systemd enmascarando bucles de bloqueo.
    Con Restart=always, systemd reiniciará la puerta de enlace incondicionalmente, incluyendo después de openclaw gateway stop (que sale limpiamente). Esto lleva a confusión donde los operadores creen que han detenido la puerta de enlace pero se reinicia en segundos. Usar Restart=on-failure para gestión systemd de producción.
  • Error: Enlace solo loopback (127.0.0.1) con Bonjour activado.
    Los paquetes multicast mDNS requieren una interfaz no-loopback enrutable. En sistemas que están exclusivamente enlazados a loopback (p.ej., runners de CI, servidores headless, VMs con solo lo y una interfaz privada), la fase de probing mDNS nunca se completará. La puerta de enlace debería detectar automáticamente esto y establecer bonjour.enabled: false cuando host sea 127.0.0.1 o ::1, pero a partir de 2026.2.26 (bc50708) esta detección está ausente.
  • Error: Hosts ARM64 (aarch64) y diferencias en la pila mDNS.
    El problema fue reportado en arm64 (Ubuntu 24, kernel 6.17.0-1008-nvidia). Algunas configuraciones ARM64 de Ubuntu se envían con systemd-resolved manejando mDNS en un modo que suprime el multicast en la interfaz loopback de manera más agresiva que las configuraciones x86_64. El estado `probing` mDNS puede resolverse correctamente en hosts x86_64 con interfaces LAN activas, lo que hace que este error dependa de la arquitectura y la topología de red.
  • Error: delivery.mode ausente en trabajos con payloads systemEvent — no se emite advertencia.
    Los trabajos cron creados con payload.kind: "systemEvent" no reciben un delivery.mode predeterminado y no emiten ninguna advertencia sobre esta omisión. Estos trabajos dependerán silenciosamente del canal de sesión principal. Auditar todos los trabajos systemEvent: openclaw cron list --filter payload.kind=systemEvent | grep -v delivery.mode y agregar modos de entrega explícitos.
  • Error: Archivo de bloqueo obsoleto después de terminación forzada que previene reinicio limpio.
    Si el proceso de la puerta de enlace se termina con SIGKILL (en lugar de SIGTERM), el archivo de bloqueo en ~/.local/share/openclaw/gateway.lock (o /tmp/openclaw-gateway.lock dependiendo del tipo de instalación) puede no limpiarse. Los intentos posteriores de inicio fallarán con lock timeout after 5000ms. Siempre remover el archivo de bloqueo manualmente después de una terminación forzada antes de reiniciar.
  • Error: Usuarios de macOS ejecutando Avahi vs. diferencias del demonio mDNS de Apple.
    En macOS, Bonjour está respaldado por el mDNSResponder nativo de Apple, que maneja mDNS de loopback de manera diferente al avahi-daemon de Linux. El estancamiento en `probing` descrito aquí es específico de entornos Linux/Avahi. Los usuarios de macOS pueden no reproducir este problema pero pueden experimentar una variante relacionada donde mDNSResponder entra en conflicto con la pila Bonjour embebida de la puerta de enlace si ambos intentan registrar el mismo nombre de servicio.
  • Error: Despliegues en contenedores Docker compartiendo el espacio de nombres de red del host.
    En contenedores Docker usando --network host, el comportamiento mDNS depende de la configuración de interfaz del host. Los contenedores usando redes bridge u overlay pueden suprimir el multicast por completo, causando síntomas idénticos de estancamiento en `probing`. Asegurar que bonjour.enabled: false esté configurado en todos los despliegues contenedorizados independientemente del modo de red.

🔗 Errores relacionados

  • Gateway failed to start: gateway already running (pid XXXXX); lock timeout after 5000ms
    Se emite cuando la ruta de reinicio interno del watchdog intenta adquirir el archivo de bloqueo de la puerta de enlace mientras el proceso existente lo tiene. También aparece independientemente cuando un usuario ejecuta openclaw gateway start mientras una puerta de enlace ya está en ejecución. No siempre es indicativo del watchdog de Bonjour — verificar la advertencia precedente bonjour watchdog detected non-announced service para confirmar el escenario del bucle.
  • Port 18789 is already in use. pid XXXXX
    Se emite inmediatamente después del timeout del archivo de bloqueo cuando el intento de inicio secundario intenta vincular el puerto RPC. De forma aislada, este error también puede aparecer después de un apagado no graceful de la puerta de enlace que deja un socket en estado TIME_WAIT. Verificar con ss -tlnp sport = :18789 si el puerto está ocupado por el proceso de la puerta de enlace u otro proceso.
  • bonjour watchdog detected non-announced service; attempting re-advertise (state=probing)
    Error de síntoma principal. También puede aparecer transitoriamente (una o dos veces) en reinicios legítimos de la puerta de enlace mientras la pila mDNS vuelve a entrar en la fase de probe. Se vuelve patológico solo cuando se repite en un intervalo — confirmar con las marcas de tiempo de los logs que el período es ≈11 segundos.
  • cron delivery failed: session channel unavailable (target=main)
    Un error secundario que puede aparecer en modos de log verbosos cuando el canal de sesión principal está en un estado degradado causado por el bucle del watchdog. No se emite en niveles de log predeterminados, razón por la cual los fallos de entrega parecen silenciosos para la mayoría de los usuarios.
  • RPC handshake timeout after 3000ms
    Puede aparecer si el bucle del watchdog causa un reinicio momentáneo del estado interno durante el cual el listener RPC está brevemente no disponible. Distincto del error principal del bucle pero puede aparecer entrelazado en los logs durante ciclos de watchdog de alta frecuencia.
  • Histórico: Conflicto de nombre mDNS en despliegues multi-instancia (pre-2025.8.x)
    En versiones anteriores, ejecutar múltiples instancias de la puerta de enlace en la misma LAN causaba colisiones de nombre mDNS que producían una variante state=conflict de la misma advertencia del watchdog. La falla del watchdog para distinguir probing de conflict es una continuación de la misma brecha arquitectónica.
  • Histórico: Compuesto de doble-reinicio de openclaw-watchdog.timer (pre-2026.1.x)
    Antes de que el temporizador watchdog externo se desacoplara del watchdog interno de la puerta de enlace, ambos temporizadores se disparaban independientemente, causando hasta dos intentos de reinicio por ciclo de ~11 segundos. Los usuarios en versiones anteriores pueden observar una frecuencia de error duplicada (aproximadamente cada 5-6 segundos).

Evidencia y fuentes

Esta guía de solución de problemas fue sintetizada automáticamente por la tubería de inteligencia de FixClaw a partir de las discusiones de la comunidad.