Standardmäßige heartbeat.target-Konfiguration verwirft Coding-Agent-Benachrichtigungen - Coding-Agent Skill Notifications Silently Dropped Due to Default heartbeat.target Configuration
Hintergrund-Task-Abschlussbenachrichtigungen vom Coding-Agent-Skill schlagen stillschweigend fehl, da heartbeat.target standardmäßig auf 'none' gesetzt ist, was dazu führt, dass LLM-Antworten vor der Zustellung verworfen werden.
🔍 Symptome
Hauptmanifestation
Wenn eine Hintergrundaufgabe des Coding-Agent abgeschlossen wird, arrive die erwartete Benachrichtigungsnachricht in keinem Kanal (Terminal, UI oder externe Integration).
Technische Fehlerausgabe
Der Heartbeat wird ausgelöst und die LLM generiert eine Antwort, aber die Zustellung wird stillschweigend unterdrückt. Es wird kein Fehler in der Konsole protokolliert. Bei der Inspektion im Debug-Modus zeigt sich:
// 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)Reproduktionsschritte
# 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: []Sekundäre Indikatoren
- Der
maybeNotifyOnExit()Handler für Hintergrundprozesse zeigt ebenfalls ein stillschweigendes Versagen - Manuelle Ausführung von
openclaw system event --text "Test" --mode nowerzeugt keine sichtbare Ausgabe - Die Konfigurationsinspektion bestätigt, dass kein
heartbeat.targetOverride in der Benutzerkonfigurationsdatei existiert
🧠 Ursache
Architektonische Übersicht
Der Benachrichtigungsfluss umfasst drei miteinander verbundene Komponenten:
┌─────────────────────────────────────────────────────────────────────┐
│ 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 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘Fehlersequenz
Schritt 1: Standardkonfiguration
Die Konfigurationsoption heartbeat.target ist standardmäßig auf "none" in src/config/defaults.ts eingestellt:
// src/config/defaults.ts (line ~47)
export const defaultConfig = {
// ...
heartbeat: {
target: "none", // ← THIS IS THE CULPRIT
interval: 30000,
// ...
},
// ...
};Schritt 2: Auflösung des Zustellungsziels
Wenn resolveHeartbeatDeliveryTarget() aufgerufen wird, liest es die Konfiguration:
// 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" });
}
}Schritt 3: Stillscheigendes Verwerfen
Das NoHeartbeatDeliveryTarget Objekt weist das Zustellungssubsystem an:
// 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 };
}
};
}Schritt 4: Betroffene Codepfade
Sowohl der Coding-Agent Skill als auch interne Handler teilen diesen Codepfad:
// 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();
}
}Warum dies nicht erkannt wurde
- Der Heartbeat-Mechanismus wurde primär für internes Timing entworfen, nicht für benutzerorientierte Benachrichtigungen
- Die Standardeinstellung
"none"gewährleistet einen lautlosen Betrieb für Hintergrund-Heartbeat-Wartungsaufgaben - Der Coding-Agent Skill wurde später hinzugefügt, ohne Kenntnis dieser Zustellungseinschränkung
- Es existiert keine Validierungswarnung, wenn ein Skill heartbeat-auslösende Befehle ohne ordnungsgemäße Konfiguration verwendet
🛠️ Schritt-für-Schritt-Lösung
Lösung A: heartbeat.target konfigurieren (Für Benutzer empfohlen)
Schritt 1: Aktuelle Konfiguration prüfen
# 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: noneSchritt 2: Konfiguration aktualisieren
Für globale Konfiguration (~/.config/openclaw/config.yaml):
# Before (default)
# No heartbeat.target entry (or implicit "none")
# After
heartbeat:
target: "last"
interval: 30000Über CLI:
openclaw config set heartbeat.target last
# Output: Configuration updated successfully
# Verify
openclaw config get heartbeat.target
# Output: lastFür Projektkonfiguration (openclaw.yaml im Arbeitsbereich):
# openclaw.yaml
# Before
version: "1"
# After
version: "1"
heartbeat:
target: "last"Schritt 3: OpenClaw Daemon neu starten (falls aktiv)
# For Homebrew-installed OpenClaw
brew services restart openclaw
# For npm-installed
openclaw daemon stop
openclaw daemon startLösung B: Session-Ziel verwenden (Für Multi-User-Setups)
Bei Ausführung in einer Multi-User- oder sitzungsbasierten Umgebung:
# openclaw.yaml
version: "1"
heartbeat:
target: "session" # Delivers to originating session instead of last channel
interval: 30000Lösung C: Coding-Agent Skill modifizieren (Für Entwickler)
Wenn Sie den Skill kontrollieren und eine Benutzerkonfiguration vermeiden möchten:
# 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
\`\`\`Lösung D: Start-Up-Validierungswarnung hinzufügen (Für Framework-Betreuer)
Fügen Sie eine Prüfung im Skill-Lader hinzu, um Benutzer zu warnen, wenn erforderliche Konfiguration fehlt:
// 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.`
);
}
}
}🧪 Verifizierung
Test 1: Konfiguration korrekt angewendet
# Verify config is set
openclaw config get heartbeat.target
# Expected: "last"
# Verify full heartbeat config
openclaw config get heartbeat
# Expected: { "target": "last", "interval": 30000 }Test 2: Manuelle System-Ereigniszustellung
# 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 notificationTest 3: Coding-Agent Hintergrundaufgaben-Benachrichtigung
# 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 successfullyTest 4: Integrationstest-Skript
#!/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 0Erwartete Exit-Codes
| Test | Erfolgs-Exit-Code | Fehler-Exit-Code |
|---|---|---|
| Konfigurationsprüfung | 0 | 1 |
| Manuelles Ereignis | 0 | 1 |
| Hintergrundaufgabe | 0 | 1 |
⚠️ Häufige Fehler
Fehler 1: Konfigurationsdatei-Speicherort Priorität
OpenClaw liest Konfiguration aus mehreren Speicherorten. Falscher Speicherort führt dazu, dass Änderungen ignoriert werden.
# 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"Fehler 2: Docker/Container Umgebungsvariablen-Überschreibung
In Docker-Deployments können Umgebungsvariablen Konfigurationsdateien überschatten.
# 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 fileDocker Compose Beispiel:
# 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 fileFehler 3: Daemon nach Konfigurationsänderung nicht neu gestartet
Konfigurationsänderungen erfordern einen Neustart des Daemons, um wirksam zu werden.
# 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 configFehler 4: macOS Homebrew Service nicht neu gestartet
# 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 heartbeatFehler 5: Zustellung in nicht-interaktiver Sitzung
Bei Ausführung im nicht-interaktiven Modus kann "last" an einen anderen Kanal liefern als erwartet.
# 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 passedFehler 6: Konfligierende Skills überschreiben Konfiguration
Einige Skills können programmatisch heartbeat.target auf "none" für ihre eigenen Zwecke setzen.
# Check if any skill modifies heartbeat config
grep -r "heartbeat.target" skills/
# or
grep -r "config.set.*heartbeat" ~/.local/share/openclaw/skills/Fehler 7: Versionsinkompatibilität
Die Optionen "last" und "session" wurden in einer bestimmten Version hinzugefügt. Bei Verwendung einer älteren Version wird die Konfiguration stillschweigend ignoriert.
# 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🔗 Zugehörige Fehler
Verbundene Fehlercodes und historische Probleme
HEARTBEAT_NO_TARGET— Interner Fehler, wenn Heartbeat ausgelöst wird aber kein Zustellungsziel existiert. Manifestiert sich als stillschweigendes Versagen in Standardkonfigurationen.ENQUEUESYSTEM_EVENT_DROPPED— Tritt auf, wenn System-Events während des Daemon-Herunterfahrens eingereiht werden oder wenn das Heartbeat-System deaktiviert ist.BACKGROUND_EXEC_NOTIFY_FAILED— Bezogen auf das Versagen vonmaybeNotifyOnExit()beim Zustellen von Abschlussbenachrichtigungen. Teilt dieselbe Ursache wie dieses Problem.SKILL_NOTIFICATION_TIMEOUT— Agents, die auf Abschlussbenachrichtigungen warten, können ein Timeout erreichen, wennheartbeat.targetauf"none"gesetzt ist, was dazu führt, dass die Skill-Ausführung als hängend erscheint.- Issue #1847 — "Background task notifications not working in v0.14.2" — Ursprünglicher Bericht über diese Art von Problem.
- Issue #2103 — "maybeNotifyOnExit silently fails when heartbeat.target is none" — Bestätigt im Upstream.
- Issue #2256 — "Documentation does not mention heartbeat.target default value" — Tracking-Issue für Dokumentationslücke.
- Issue #2389 — "Coding-agent skill unusable without manual config" — Feature-Anfrage zur Behebung des Standardverhaltens.
Zugehörige Konfigurationsoptionen
# 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 busyDokumentationsreferenzen
docs/gateway/heartbeat.md— Dokumentiert die Heartbeat-Architektur, unterschlägt aber die Auswirkung des"none"Standardwerts auf Benachrichtigungenskills/coding-agent/SKILL.md— Referenziert denopenclaw system eventBefehl ohne die Konfigurationsanforderung zu erwähnendocs/config/reference.md— Listet dieheartbeat.targetOptionen auf, erklärt aber nicht die Auswirkungen auf Benachrichtigungen