April 20, 2026 • Version: v0.14.x - v1.x

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 now erzeugt keine sichtbare Ausgabe
  • Die Konfigurationsinspektion bestätigt, dass kein heartbeat.target Override 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

  1. Der Heartbeat-Mechanismus wurde primär für internes Timing entworfen, nicht für benutzerorientierte Benachrichtigungen
  2. Die Standardeinstellung "none" gewährleistet einen lautlosen Betrieb für Hintergrund-Heartbeat-Wartungsaufgaben
  3. Der Coding-Agent Skill wurde später hinzugefügt, ohne Kenntnis dieser Zustellungseinschränkung
  4. 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: none

Schritt 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: last

Fü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 start

Lö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: 30000

Lö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 notification

Test 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 successfully

Test 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 0

Erwartete Exit-Codes

TestErfolgs-Exit-CodeFehler-Exit-Code
Konfigurationsprüfung01
Manuelles Ereignis01
Hintergrundaufgabe01

⚠️ 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 file

Docker 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 file

Fehler 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 config

Fehler 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 heartbeat

Fehler 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 passed

Fehler 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 von maybeNotifyOnExit() beim Zustellen von Abschlussbenachrichtigungen. Teilt dieselbe Ursache wie dieses Problem.
  • SKILL_NOTIFICATION_TIMEOUT — Agents, die auf Abschlussbenachrichtigungen warten, können ein Timeout erreichen, wenn heartbeat.target auf "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 busy

Dokumentationsreferenzen

  • docs/gateway/heartbeat.md — Dokumentiert die Heartbeat-Architektur, unterschlägt aber die Auswirkung des "none" Standardwerts auf Benachrichtigungen
  • skills/coding-agent/SKILL.md — Referenziert den openclaw system event Befehl ohne die Konfigurationsanforderung zu erwähnen
  • docs/config/reference.md — Listet die heartbeat.target Optionen auf, erklärt aber nicht die Auswirkungen auf Benachrichtigungen

Belege & Quellen

Diese Troubleshooting-Anleitung wurde automatisch von der FixClaw Intelligence Pipeline aus Community-Diskussionen synthetisiert.