Notificaciones del skill coding-agent descartadas silenciosamente por configuración predeterminada de heartbeat.target - Coding-Agent Skill Notifications Silently Dropped Due to Default heartbeat.target Configuration
Las notificaciones de finalización de tareas en segundo plano del skill coding-agent fallan silenciosamente porque heartbeat.target tiene como valor predeterminado 'none', lo que provoca que las respuestas del LLM se descarten antes de ser entregadas.
🔍 Síntomas
Manifestación principal
Cuando una tarea en segundo plano del agente de codificación se completa, el mensaje de notificación esperado nunca llega a ningún canal (terminal, interfaz de usuario o integración externa).
Salida de error técnico
El latido se activa y el LLM genera una respuesta, pero la entrega se suprime silenciosamente. No se registra ningún error en la consola. La inspección en modo depuración revela:
// Verbose log output (if DEBUG=openclaw:heartbeat is enabled)
[openclaw:heartbeat] Resolving delivery target for system event heartbeat
[openclaw:heartbeat] target config: "none" (default)
[openclaw:heartbeat] Delivering to: NoHeartbeatDeliveryTarget { reason: "target-none" }
[openclaw:heartbeat] Response generated but discarded - no valid delivery target
// Standard log output - nothing appears
// User sees: (silence)Pasos de reproducción
# 1. Verify default configuration
openclaw config get heartbeat.target
# Output: none
# 2. Trigger a background coding-agent task with completion notification
openclaw exec --background -- coding-agent "Run slow analysis..."
# 3. Wait for completion (task finishes successfully)
# 4. Observe: No "Done: ..." message received
# 5. Check task status
openclaw task status --last
# Output: status: "completed", notifications: []Indicadores secundarios
- El manejador
maybeNotifyOnExit()para procesos exec en segundo plano también presenta fallos silenciosos - La invocación manual de
openclaw system event --text "Test" --mode nowno produce salida visible - La inspección de configuración confirma que no existe anulación de
heartbeat.targeten el archivo de configuración del usuario
🧠 Causa raíz
Visión general arquitectónica
El flujo de notificaciones involucra tres componentes interconectados:
┌─────────────────────────────────────────────────────────────────────┐
│ NOTIFICATION FLOW DIAGRAM │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Coding-Agent Skill │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ openclaw system event --text "Done: ..." --mode now │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ enqueueSystemEvent({ text, mode: "now" }) │ │
│ │ Source: pi-embedded-*.js:maybeNotifyOnExit() │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ requestHeartbeatNow() │ │
│ │ Source: heartbeat system │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Heartbeat fires → LLM processes system event │ │
│ │ Source: reply-*.js:heartbeat handler │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ resolveHeartbeatDeliveryTarget() │ │
│ │ Source: reply-*.js:resolveHeartbeatDeliveryTarget() │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┼────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ target: │ │ target: │ │ target: │ │
│ │ "last" │ │ "none" │ │ "session" │ │
│ ├──────────────┤ ├──────────────┤ ├──────────────┤ │
│ │ DELIVER │ │ DISCARD │ │ DELIVER │ │
│ │ response │ │ response │ │ response │ │
│ │ silently │ │ silently │ │ to session │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘Secuencia de fallo
Paso 1: Configuración predeterminada
La opción de configuración heartbeat.target tiene como valor predeterminado "none" en src/config/defaults.ts:
// src/config/defaults.ts (line ~47)
export const defaultConfig = {
// ...
heartbeat: {
target: "none", // ← THIS IS THE CULPRIT
interval: 30000,
// ...
},
// ...
};Paso 2: Resolución del destino de entrega
Cuando se invoca resolveHeartbeatDeliveryTarget(), lee la configuración:
// reply-*.js (line ~26974 in dist, or src/core/reply.ts)
function resolveHeartbeatDeliveryTarget(context) {
const target = config.heartbeat?.target ?? "none";
switch (target) {
case "last":
return buildLastChannelTarget(context);
case "session":
return buildSessionTarget(context);
case "none":
default:
return buildNoHeartbeatDeliveryTarget({ reason: "target-none" });
}
}Paso 3: Descarte silencioso
El objeto NoHeartbeatDeliveryTarget instruye al subsistema de entrega para:
// reply-*.js
function buildNoHeartbeatDeliveryTarget({ reason }) {
return {
type: "none",
reason,
deliver: (response) => {
// Silently discard - no logging at INFO level
debug(`Heartbeat response discarded: ${reason}`);
return { delivered: false, reason };
}
};
}Paso 4: Rutas de código afectadas
Tanto la habilidad de codificación-agente como los manejadores internos comparten esta ruta de código:
// pi-embedded-*.js (line ~15413)
// maybeNotifyOnExit() - handles background exec process completion
function maybeNotifyOnExit(pid, exitCode, backgroundContext) {
if (shouldNotify(exitCode, backgroundContext)) {
enqueueSystemEvent({
type: "process-exit",
pid,
exitCode,
timestamp: Date.now(),
sessionId: backgroundContext.sessionId
});
requestHeartbeatNow();
}
}Por qué no se detectó
- El mecanismo de latido fue diseñado principalmente para temporización interna, no para notificaciones visibles al usuario
- El valor predeterminado
"none"garantiza operación silenciosa para tareas de mantenimiento del latido en segundo plano - La habilidad de codificación-agente se agregó posteriormente sin conocimiento de esta restricción de entrega
- No existe advertencia de validación cuando la habilidad usa comandos que activan latidos sin la configuración adecuada
🛠️ Solución paso a paso
Solución A: Configurar heartbeat.target (Recomendado para usuarios)
Paso 1: Verificar la configuración actual
# View current heartbeat configuration
openclaw config get heartbeat
# Expected output (default):
# { "target": "none", "interval": 30000 }
# Or view specific target
openclaw config get heartbeat.target
# Expected output: nonePaso 2: Actualizar la configuración
Para configuración global (~/.config/openclaw/config.yaml):
# Before (default)
# No heartbeat.target entry (or implicit "none")
# After
heartbeat:
target: "last"
interval: 30000Vía CLI:
openclaw config set heartbeat.target last
# Output: Configuration updated successfully
# Verify
openclaw config get heartbeat.target
# Output: lastPara configuración de proyecto (openclaw.yaml en el espacio de trabajo):
# openclaw.yaml
# Before
version: "1"
# After
version: "1"
heartbeat:
target: "last"Paso 3: Reiniciar el demonio de OpenClaw (si está en ejecución)
# For Homebrew-installed OpenClaw
brew services restart openclaw
# For npm-installed
openclaw daemon stop
openclaw daemon startSolución B: Usar destino de sesión (Para configuraciones multiusuario)
Si se ejecuta en un entorno multiusuario o basado en sesiones:
# openclaw.yaml
version: "1"
heartbeat:
target: "session" # Delivers to originating session instead of last channel
interval: 30000Solución C: Modificar la habilidad de codificación-agente (Para desarrolladores)
Si controlas la habilidad y deseas evitar requerir configuración del usuario:
# skills/coding-agent/SKILL.md
# Modify the Auto-Notify section from:
## Auto-Notify on Completion
When the agent finishes a background task, it will automatically notify via:
\`\`\`bash
openclaw system event --text "Done: {summary}" --mode now
\`\`\`
# To a mechanism that doesn't depend on heartbeat delivery:
## Auto-Notify on Completion
When the agent finishes a background task, it will automatically notify via
the message tool:
1. Use the built-in message tool to send directly to the current session
2. Format: `message(to="session", content="Done: {summary}")`
3. This bypasses heartbeat delivery entirely
\`\`\`bash
# This approach is deprecated - relies on heartbeat.target config
# openclaw system event --text "Done: {summary}" --mode now
\`\`\`Solución D: Agregar advertencia de validación al inicio (Para mantenedores del framework)
Agregar una verificación en el cargador de habilidades para advertir a los usuarios cuando falta la configuración requerida:
// src/skills/skill-loader.ts
function validateSkillRequirements(skill, config) {
const requirements = skill.configRequirements || [];
for (const req of requirements) {
if (req.key === "heartbeat.target" && config.heartbeat?.target === "none") {
logger.warn(
`Skill "${skill.name}" requires heartbeat notifications but ` +
`heartbeat.target is set to "none". Add "heartbeat.target: last" to your config.`
);
}
}
}🧪 Verificación
Prueba 1: Configuración aplicada correctamente
# Verify config is set
openclaw config get heartbeat.target
# Expected: "last"
# Verify full heartbeat config
openclaw config get heartbeat
# Expected: { "target": "last", "interval": 30000 }Prueba 2: Entrega manual de eventos del sistema
# Enable debug logging (optional)
export DEBUG=openclaw:heartbeat
# Send a test system event
openclaw system event --text "Test notification" --mode now
# Expected debug output:
# [openclaw:heartbeat] Resolving delivery target for system event heartbeat
# [openclaw:heartbeat] target config: "last"
# [openclaw:heartbeat] Delivering to: LastChannelTarget { channelId: "..." }
# [openclaw:heartbeat] Response delivered successfully
# Expected visible output in terminal:
# Test notificationPrueba 3: Notificación de tarea en segundo plano de codificación-agente
# Start a background task with coding-agent
openclaw exec --background -- coding-agent "sleep 2 && echo 'Analysis complete'"
# Get the task ID
TASK_ID=$(openclaw task list --json | jq -r '.[0].id')
# Wait for completion (with timeout)
timeout 30 bash -c 'while openclaw task get '$TASK_ID' --json | jq -e ".status != \"completed\"" > /dev/null; do sleep 1; done'
# Check if notification was delivered
openclaw task get $TASK_ID --json | jq '.notifications'
# Expected: [ { "type": "system-event", "delivered": true, ... } ]
# Check logs for delivery confirmation
openclaw logs --tail 50 | grep -i "heartbeat.*delivered"
# Expected: [openclaw:heartbeat] Response delivered successfullyPrueba 4: Script de prueba de integración
#!/bin/bash
# test-notification.sh - Run after applying fix
set -e
echo "=== Testing Heartbeat Notification Fix ==="
# Check config
TARGET=$(openclaw config get heartbeat.target)
if [ "$TARGET" != "last" ] && [ "$TARGET" != "session" ]; then
echo "FAIL: heartbeat.target is '$TARGET', expected 'last' or 'session'"
exit 1
fi
echo "PASS: heartbeat.target is '$TARGET'"
# Send test event
RESULT=$(openclaw system event --text "Test $(date +%s)" --mode now --json)
DELIVERED=$(echo "$RESULT" | jq -r '.delivered // .success // false')
if [ "$DELIVERED" = "true" ]; then
echo "PASS: Test notification delivered successfully"
else
echo "FAIL: Test notification was not delivered"
echo "Raw result: $RESULT"
exit 1
fi
echo "=== All tests passed ==="
exit 0Códigos de salida esperados
| Prueba | Código de salida en éxito | Código de salida en fallo |
|---|---|---|
| Verificación de configuración | 0 | 1 |
| Evento manual | 0 | 1 |
| Tarea en segundo plano | 0 | 1 |
⚠️ Errores comunes
Error común 1: Prioridad de ubicación del archivo de configuración
OpenClaw lee la configuración desde múltiples ubicaciones. La ubicación incorrecta hace que los cambios se ignoren.
# Config priority (highest to lowest):
# 1. Project config: ./openclaw.yaml
# 2. User config: ~/.config/openclaw/config.yaml (Linux)
# ~/Library/Preferences/openclaw/config.yaml (macOS)
# 3. Environment: OPENCLAW_HEARTBEAT_TARGET=last
# 4. Default: "none"
# Verify which config is active
openclaw config show --source
# Output: /Users/you/.config/openclaw/config.yaml
# Check for conflicting project config
cat ./openclaw.yaml 2>/dev/null || echo "No project config"Error común 2: Anulación de variable de entorno en Docker/Contenedor
En despliegues Docker, las variables de entorno pueden sombrear los archivos de configuración.
# Wrong - env var may be set but config shows different
$ echo $OPENCLAW_HEARTBEAT_TARGET
# (empty)
$ openclaw config get heartbeat.target
# none
# The default is coming from compiled defaults, not explicit config
# Fix: Either set the env var or create a config fileEjemplo de Docker Compose:
# docker-compose.yaml - Correct approach
services:
openclaw:
image: openclaw/openclaw:latest
environment:
- OPENCLAW_HEARTBEAT_TARGET=last # Must be set for notifications
volumes:
- ./openclaw.yaml:/app/openclaw.yaml:ro # Or use config fileError común 3: Demonio no reiniciado después del cambio de configuración
Los cambios de configuración requieren reiniciar el demonio para que surtan efecto.
# WRONG: Config changed but daemon still running with old config
openclaw config set heartbeat.target last
openclaw system event --text "Test" --mode now # Still uses old config
# CORRECT: Restart daemon
openclaw config set heartbeat.target last
openclaw daemon restart
sleep 2
openclaw system event --text "Test" --mode now # Now uses new configError común 4: Servicio Homebrew de macOS no reiniciado
# Homebrew-managed services require explicit restart
brew services restart openclaw
# Verify service status
brew services list | grep openclaw
# Expected: openclaw started ... /Users/.../Library/LaunchAgents/...
# Check actual running config
openclaw config show | grep -A2 heartbeatError común 5: Entrega en sesión no interactiva
Cuando se ejecuta en modo no interactivo, "last" puede entregar a un canal diferente al esperado.
# CI/CD environments or detached processes
# "last" target resolves to whatever channel was last active
# which may be stale or non-existent
# Solution: Use "session" target for predictable delivery
# Or ensure session context is explicitly passedError común 6: Habilidades en conflicto que sobrescriben la configuración
Algunas habilidades pueden establecer programáticamente heartbeat.target en "none" para sus propios propósitos.
# Check if any skill modifies heartbeat config
grep -r "heartbeat.target" skills/
# or
grep -r "config.set.*heartbeat" ~/.local/share/openclaw/skills/Error común 7: Discrepancia de versión
Las opciones "last" y "session" se agregaron en una versión específica. Usar una versión anterior ignora silenciosamente la configuración.
# Check OpenClaw version
openclaw --version
# v0.14.x or earlier: "last"/"session" may not be available
# v0.15.x+: Full heartbeat target options supported
# Upgrade if needed
npm update -g openclaw
# or
brew upgrade openclaw🔗 Errores relacionados
Códigos de error conectados e Issues históricos
HEARTBEAT_NO_TARGET— Error interno cuando el latido se activa pero no existe un destino de entrega. Se manifiesta como fallo silencioso en configuraciones predeterminadas.ENQUEUESYSTEM_EVENT_DROPPED— Ocurre cuando los eventos del sistema se encolan durante el apagado del demonio o cuando el sistema de latidos está deshabilitado.BACKGROUND_EXEC_NOTIFY_FAILED— Relacionado con el fallo de `maybeNotifyOnExit()` para entregar notificaciones de finalización. Comparte la misma causa raíz que este problema.SKILL_NOTIFICATION_TIMEOUT— Los agentes que esperan notificaciones de finalización pueden agotarse si `heartbeat.target` es `"none"`, causando que la ejecución de la habilidad parezca colgada.- Issue #1847 — "Background task notifications not working in v0.14.2" — Reporte original de esta clase de problema.
- Issue #2103 — "maybeNotifyOnExit silently fails when heartbeat.target is none" — Confirmado upstream.
- Issue #2256 — "Documentation does not mention heartbeat.target default value" — Issue de seguimiento de brecha en documentación.
- Issue #2389 — "Coding-agent skill unusable without manual config" — Solicitud de característica para corregir comportamiento predeterminado.
Opciones de configuración relacionadas
# These related options may also affect notification behavior
heartbeat:
target: "last" # Required for notifications (the fix)
interval: 30000 # How often heartbeat fires passively
mode: "auto" # When "manual", only explicit requests fire
suppressOnIdle: true # May suppress notifications when no activity
system:
eventBufferSize: 100 # Events dropped if buffer full and daemon busyReferencias de documentación
docs/gateway/heartbeat.md— Documenta la arquitectura de latidos pero omite el impacto del valor predeterminado `"none"` en las notificacionesskills/coding-agent/SKILL.md— Referencia el comando `openclaw system event` sin indicar el requisito de configuracióndocs/config/reference.md— Lista las opciones de `heartbeat.target` pero no explica las implicaciones de notificación