Agregar filtro maxMessageAge para descartar webhooks BlueBubbles reentregados obsoletos - Adding maxMessageAge Filter to Drop Stale Re-delivered BlueBubbles Webhooks
Cómo configurar el canal BlueBubbles de OpenClaw para descartar silenciosamente mensajes entrantes mayores a un umbral configurable, previniendo el procesamiento de webhooks obsoletos y respuestas de error internas.
🔍 Síntomas
Comportamiento observado
Cuando BlueBubbles reenvía webhooks de mensajes antiguos, ocurren los siguientes síntomas:
- Procesamiento de mensajes obsoletos: Mensajes con marcas de tiempo de horas o días en el pasado aparecen en el flujo de conversación del agente como si fueran nuevos
- Respuestas erróneas: El agente genera respuestas contextuales a mensajes antiguos, confundiendo a los usuarios
- Respuestas de error de límite de tasa: Cuando el servicio de IA devuelve una respuesta 529 (límite de tasa), el texto de error sin procesar se envía como respuesta de iMessage:
The AI service is temporarily overloaded. Please try again in a moment.
Manifestaciones técnicas
Las cargas útiles de webhook problemáticas contienen valores dateCreated que no se alinean con la hora actual:
json { “id”: “msg-abc123”, “dateCreated”: “2026-01-15T08:30:00Z”, “text”: “Hello from yesterday”, “guid”: “some-guid” }
Cuando la hora actual del servidor es 2026-02-24T14:00:00Z, este mensaje tiene 40 días de antigüedad pero aún se procesa a través de la tubería estándar del controlador de entrada.
🧠 Causa raíz
Problema arquitectónico
El controlador de entrada de BlueBubbles carece de validación temporal en la capa de aceptación de mensajes. La tubería de procesamiento de webhook tiene dos brechas críticas:
- Sin validación de umbral de edad: El controlador procesa cualquier mensaje entrante independientemente de su marca de tiempo `dateCreated`. BlueBubbles ocasionalmente reenvía mensajes de su cola interna, enviando cargas útiles con marcas de tiempo muy lejanas en el pasado como si fueran mensajes entrantes nuevos.
- Fuga de mensajes de error internos: Los mensajes de error generados por el middleware de manejo de errores interno (particularmente respuestas de límite de tasa 529) se enrutan a través de la misma tubería de respuesta que los mensajes legítimos del usuario, causando que el texto de error sin procesar se envíe como respuestas de iMessage.
Secuencia de fallo
- BlueBubbles detecta un mensaje antiguo en su cola
- BlueBubbles envía POST de webhook al punto de conexión de entrada de OpenClaw
- El controlador extrae el mensaje, extrae dateCreated: “2026-01-15T08:30:00Z”
- NO se realiza verificación de edad → el mensaje entra en la tubería de procesamiento del agente
- El agente genera una respuesta basada en contexto obsoleto
- La respuesta se envía a través del canal de respuesta de BlueBubbles como iMessage
O (para caso de error):
- El servicio de IA devuelve error de límite de tasa 529
- El middleware de error genera texto de error legible para humanos
- El texto de error fluye a través del canal de respuesta en lugar de ser registrado
- El usuario recibe “The AI service is temporarily overloaded…” como mensaje de texto
Análisis de ruta de código
El problema reside en la lógica de aceptación de mensajes dentro del controlador de BlueBubbles. Sin un campo configurable maxMessageAgeSec, no hay mecanismo para:
- Comparar la `dateCreated` del mensaje con la hora actual del servidor
- Determinar si el tiempo transcurrido excede un umbral configurado
- Cortocircuitar la tubería de procesamiento con un descarte silencioso (registrado a nivel DEBUG)
🛠️ Solución paso a paso
Adición de configuración
Agregue el campo maxMessageAgeSec a su archivo de configuración de OpenClaw (config.json o configuración basada en entorno):
json { “channels”: { “bluebubbles”: { “serverUrl”: “https://your-bluebubbles-server.local”, “password”: “your-password-here”, “maxMessageAgeSec”: 300 } } }
Antes vs Después de la configuración
Antes (sin filtrado de edad):
json { “channels”: { “bluebubbles”: { “serverUrl”: “https://bb-server.local”, “password”: “secret123” } } }
Después (con filtrado de edad):
json { “channels”: { “bluebubbles”: { “serverUrl”: “https://bb-server.local”, “password”: “secret123”, “maxMessageAgeSec”: 300 } } }
Pasos de implementación del controlador
Para implementar esta corrección en el código base, realice las siguientes modificaciones:
- Defina la constante de validación en la interfaz de configuración del canal:
// Within channels/bluebubbles/types.ts or similar export interface BlueBubblesConfig { serverUrl: string; password: string; maxMessageAgeSec?: number; // Optional, defaults to no filtering } - Agregue validación de edad en el controlador de mensajes entrantes:
// Within the webhook handler function async function handleInboundMessage(payload: BlueBubblesWebhookPayload): Promise<void> { const config = getBlueBubblesConfig();// Validate message age if (config.maxMessageAgeSec) { const messageAge = Date.now() - new Date(payload.dateCreated).getTime(); const maxAgeMs = config.maxMessageAgeSec * 1000;
if (messageAge > maxAgeMs) { logger.debug(`Dropping stale message ${payload.guid} (age: ${Math.floor(messageAge/60000)}m)`); return; // Silent drop }}
// Continue with normal processing… await processMessage(payload); }
- Asegúrese de que los mensajes de error nunca se enruten a superficies de respuesta:
// Error middleware or handler function handleAIErrors(error: Error, context: MessageContext): void { if (error.statusCode === 529) { // Rate limit: log internally, do NOT send to user logger.warn(`AI service rate-limited for message ${context.messageId}`); return; // No reply sent }// For other errors, decide based on error visibility config if (shouldExposeErrors()) { sendReply(context, generateSafeErrorMessage(error)); } else { logger.error(error); } }
🧪 Verificación
Procedimiento de prueba para filtrado de edad de mensajes
- Implemente la configuración actualizada y reinicie el servicio OpenClaw:
# Restart OpenClaw to load new configuration sudo systemctl restart openclawCheck service status
sudo systemctl status openclaw –no-pager
- Verifique que la configuración esté cargada:
# Check logs for config load tail -50 /var/log/openclaw/openclaw.log | grep -i "bluebubbles\|maxMessageAge"Expected output:
[INFO] BlueBubbles channel initialized with maxMessageAgeSec=300
- Pruebe el manejo de mensajes obsoletos:
# Send a test webhook with old timestamp via curl curl -X POST http://localhost:3000/webhooks/bluebubbles \ -H "Content-Type: application/json" \ -H "X-BlueBubbles-Password: your-password" \ -d '{ "id": "test-stale-001", "guid": "test-stale-guid", "dateCreated": "2026-01-15T08:30:00Z", "text": "Test stale message" }'Expected: 200 OK, no reply sent, log entry for dropped message
Check logs for dropped message confirmation
tail -20 /var/log/openclaw/openclaw.log | grep “Dropping stale”
- Pruebe el manejo de mensajes nuevos (verificación de cordura):
# Send a webhook with current timestamp curl -X POST http://localhost:3000/webhooks/bluebubbles \ -H "Content-Type: application/json" \ -H "X-BlueBubbles-Password: your-password" \ -d "{ \"id\": \"test-fresh-001\", \"guid\": \"test-fresh-guid-$(date +%s)\", \"dateCreated\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\", \"text\": \"Test fresh message\" }"Expected: 200 OK, message processed normally, iMessage reply sent
Resultados esperados
- Mensajes obsoletos (más antiguos que `maxMessageAgeSec`) devuelven HTTP 200 pero producen una entrada de registro a nivel DEBUG; no se envía respuesta
- Mensajes nuevos se procesan a través de la tubería normal y reciben respuestas del agente
- Mensajes de error (límite de tasa 529) se registran pero nunca se reenvían como respuestas de iMessage
⚠️ Errores comunes
- Desajuste de zona horaria: Asegúrese de que el reloj del sistema del servidor OpenClaw esté sincronizado con BlueBubbles. Si los servidores están en diferentes zonas horarias y usan hora local para `dateCreated`, los mensajes pueden clasificarse incorrectamente como obsoletos o nuevos. Use marcas de tiempo UTC de manera consistente.
- Configuración no reiniciada: Los cambios a `config.json` no surten efecto hasta que OpenClaw se reinicie. La recarga en caliente no es compatible con cambios de configuración estructural.
- Comportamiento de sobrecarga en conflicto: En entornos macOS, la gestión de servicios `launchctl` puede no usar `systemctl`. Use `launchctl unload` / `launchctl load` en lugar de `systemctl restart`.
- Comportamiento predeterminado sin configuración: Si se omite `maxMessageAgeSec`, se procesan todos los mensajes (incluidos los obsoletos). No hay valor predeterminado integrado; debe establecer el valor explícitamente.
- Mapeo de variables de entorno de Docker: Cuando use Docker, JSON anidado en variables de entorno debe usar guiones bajos dobles para la jerarquía:
CHANNELS__BLUEBUBBLES__MAXMESSAGEAGESEC=300 - Nivel de detalle de registro: Los mensajes de descarte a nivel DEBUG pueden no aparecer en la salida de registro predeterminada. Asegúrese de que su configuración de registro esté establecida en DEBUG para el canal bluebubbles, o verifique el archivo de registro de seguimiento.
🔗 Errores relacionados
- Errores de límite de tasa 529: Respuestas de sobrecarga temporal del servicio de IA que se filtran como texto de iMessage cuando el manejo de errores no está aislado de la tubería de respuesta
- EAI_AGAIN / EHOSTUNREACH: Errores de red cuando el servidor BlueBubbles es inaccesible; estos son distintos de los problemas de edad del mensaje pero pueden aparecer en registros similares
- Procesamiento de mensajes duplicados: BlueBubbles ocasionalmente envía el mismo webhook múltiples veces; cuando se combina con filtrado de edad faltante, esto causa respuestas duplicadas del agente
- Fallos de autenticación (401): Contraseña incorrecta en la configuración puede causar que todos los webhooks sean rechazados; no relacionado con el filtrado de edad pero puede confundirse con errores de configuración
- Errores de análisis de marca de tiempo: Campos `dateCreated` mal formados en las cargas útiles de BlueBubbles pueden causar excepciones; asegúrese de un manejo elegante de formatos de fecha inválidos