Skip to main content
Heartbeat Fix — fix OpenClaw Clawdbot silent heartbeat delivery failures
v1.0 · March 20, 2026 · For OpenClaw Clawdbot

Heartbeat Fix

Your bots are alive. They just can't prove it.

OpenClaw's heartbeat system has five bugs that silently eat your bots' messages before they ever reach you. No errors. No logs. Just silence. This prompt diagnoses your entire fleet, fixes the pipeline, and gets proof-of-life messages flowing again — in one paste.

What you're experiencing

🔇openclaw system heartbeat last → null
🔇Bot running for hours — zero heartbeat messages received
🔇Heartbeat worked once, then stopped forever
🔇Some bots deliver, others don't — same config

Your bots aren't dead. OpenClaw is eating their messages before they reach you.

Sound familiar?

"I set up heartbeats but never got a single message. I assumed they weren't configured right."

"Three of my bots send heartbeats fine. The other two are silent. Same config on all of them."

"It worked for one tick, then stopped. I spent an hour debugging before giving up."

"I checked the session transcript — the bot IS responding. But the message never reaches Telegram."

Five things go wrong. Most fleets hit all five.

These aren't misconfigurations. They're bugs in OpenClaw's heartbeat pipeline that silently suppress messages at five independent points.

1

The escape hatch

OpenClaw's default prompt tells your bot: "if nothing needs attention, reply HEARTBEAT_OK." The bot obeys. OpenClaw strips "HEARTBEAT_OK" from the response and suppresses delivery if what's left is under 300 characters. Your bot did everything right — and got silenced for it.

2

Cron contamination

Your cron job responses go through the same filter that checks for HEARTBEAT_OK. If a cron response accidentally contains that text, it gets misclassified and suppressed. Cron jobs and heartbeats should have separate pipelines. They don't.

3

Queue collision

Before firing a heartbeat, OpenClaw checks: "is anything else running?" If a cron job is active at that exact moment, the heartbeat is silently skipped. When your cron and heartbeat intervals share a common factor (both at 10 minutes), they collide on a fixed schedule.

4

The dedup trap

OpenClaw suppresses heartbeat messages that look too similar to the last one. Bots with simple tasks produce "nothing to report" in slightly different words. The dedup filter sees through the paraphrasing and silently drops every message after the first. No errors. No logs.

5

The active drop

If your bot is doing ANYTHING when a heartbeat tick fires — reading a message, running a tool, processing a cron — the heartbeat is immediately dropped. Not queued. Not deferred. Dropped. This is hardcoded in OpenClaw and cannot be fixed via config.

Build Your Heartbeat Fix Prompt

Customize your heartbeat settings. The generator builds a Claude Code prompt tailored to your fleet. Copy it, paste it into Claude Code on the same machine as your bots, and let it fix everything.

Heartbeat Interval

OpenClaw default: 30m

Shorter = more proof-of-life messages but more API calls. Longer = fewer messages but lower cost.

5m60mEvery 31m

Adjusted to 31m (prime) to avoid cron collisions

Heartbeat Response Style

What your bot says on each heartbeat tick.

Live Heartbeat Prompt Preview

This is what gets written to your clawdbot.json heartbeat prompt field.

HEARTBEAT TICK. NEVER include the text HEARTBEAT_OK in your response under any circumstances. Your standing orders are in HEARTBEAT.md (already loaded in your context above). Execute every instruction in it now. Do not investigate your own configuration. Do not skip tasks. Reply with a summary of what you did. End your response with [tick:<unix_timestamp>] to ensure uniqueness.
Interval: 31m (from 30m, prime)ackMaxChars: 0Tier: Advanced

Five bugs — technical detail

1

HEARTBEAT_OK Suppression

The function stripHeartbeatToken() strips "HEARTBEAT_OK" from the response edges. If the remaining text is ≤ ackMaxChars (default: 300 characters), the entire response is marked shouldSkip: true and never reaches the transport layer.

The fix sets ackMaxChars: 0, meaning only a completely empty response gets suppressed. The heartbeat prompt explicitly prohibits the model from saying HEARTBEAT_OK, and that prohibition is placed FIRST in the prompt so it survives any truncation.

2

Cron Classification Entanglement

Cron job responses pass through the same stripHeartbeatToken pipeline via normalizeReplyPayload() and buildReplyPayloads(). Three separate normalization passes (Gates 2, 4, and 5) all check for HEARTBEAT_OK in non-heartbeat responses. The fix (ackMaxChars: 0) makes all three gates pass-through.

3

Queue Gate Collision

Gate 5 in the heartbeat scheduler: getQueueSize(Main) > 0. If anything is in the main processing queue when a heartbeat tick fires, the tick is silently skipped. When cron and heartbeat intervals share a common factor (e.g., both at 10 minutes), they collide every LCM minutes.

The fix uses a prime-number minute interval. Primes minimize shared factors with common cron values (5m, 10m, 15m, 30m). Example: heartbeat at 31m with crons at 10m → collisions only every 310 minutes (0.3% of ticks).

4

Duplicate Detection

Bots with idle or simple HEARTBEAT.md tasks produce semantically identical responses. The dedup filter suppresses messages too similar to the previous delivery within a 24-hour window. No errors are logged.

The fix appends [tick:<unix_timestamp>] to every response, making each one structurally unique. A unix timestamp is guaranteed different per tick, defeating byte-level, fuzzy, and semantic dedup.

5

Active Session Drop

resolveActiveRunQueueAction() returns "drop" when isHeartbeat === true && isActive === true. This is hardcoded — not configurable. If the bot is processing any request when a heartbeat tick arrives, the heartbeat is immediately discarded without logging.

No config-level fix exists. The prime interval offset reduces systematic collisions with crons, but random collisions cannot be prevented. Expected delivery ceiling: ~93%.

The 7-Gate Suppression Pipeline

GateFunctionWhat kills your heartbeatOur fix
1resolveActiveRunQueueAction()Bot is busy → heartbeat droppedNone (hardcoded)
2normalizeStreamingText()HEARTBEAT_OK stripped mid-streamackMaxChars: 0
3stripHeartbeatToken()Response too short after stripackMaxChars: 0
4buildReplyPayloads()HEARTBEAT_OK in non-heartbeat payloadackMaxChars: 0
5normalizeReplyPayload()Another HEARTBEAT_OK strip passackMaxChars: 0
6isRenderablePayload()Empty text after normalizationPrompt ensures content
7shouldSuppressMessagingToolReplies()Bot used messaging toolDocumented limitation

What we're asking OpenClaw to fix

  1. 1Default prompt biased toward silence
  2. 2Shared classification pipeline for cron and heartbeat
  3. 3Queue gate with no heartbeat priority
  4. 4Dedup filter on a monitoring system
  5. 5Active session drop with no deferral

The common thread: OpenClaw's heartbeat was designed for alerting (tell me when something's wrong), not monitoring (prove you're alive). These config workarounds bridge the gap until the architecture catches up.

Buy Me A Coffee