#heartbeat refuses to work

1 messages · Page 1 of 1 (latest)

pastel estuary
#

Heartbeat never works. At all. I set all parameters, OpenClaw didn't complain. Fine, right? Funny thing. It never sends any heartbeats. At all. I cannot, under any circumstance, get it to send a heartbeat. Krill, please help.

frozen burrowBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

sharp parcelBOT
#

Heartbeat “not working” is very often just “it’s running, but it’s intentionally silent”.

Two key defaults:

  • agents.defaults.heartbeat.target defaults to "none" → it runs but doesn’t deliver anywhere
  • HEARTBEAT_OK acks are hidden by default (channels.*.heartbeat.showOk: false) → even if it runs, you won’t see a message unless it has an “alert” to say

1) Fastest way to see what’s actually happening

Run these on the gateway host and paste the output:

openclaw --version
openclaw system heartbeat last
openclaw config get agents.defaults.heartbeat
openclaw config get channels.defaults.heartbeat

openclaw system heartbeat last will usually tell you the smoking gun, e.g.:

#
  • reason=quiet-hours (activeHours window)
  • reason=empty-heartbeat-file (your HEARTBEAT.md is “empty” → only headers/blank lines)
  • reason=alerts-disabled (visibility config suppresses it)
  • reason=requests-in-flight (gateway busy, heartbeat deferred)

2) Quick “make it talk” test (temporary)

If you want a heartbeat message in Discord every time just to verify delivery, set:

{
agents: { defaults: { heartbeat: { every: "5m", target: "last" } } },
channels: { defaults: { heartbeat: { showOk: true } } },
}

Then force one immediately:

pastel estuary
#

ah. i see.
"skipped"
reason: "empty heartbeat file"

sharp parcelBOT
#

That explains it.

OpenClaw will skip the entire heartbeat run when HEARTBEAT.md exists but is “effectively empty” (blank lines / only # headings) — it does this to avoid burning tokens on a no-op.

Fix options:

  1. Put at least one real checklist item in HEARTBEAT.md, e.g.
# Heartbeat

- If anything is pending or blocked, tell me what + what you need from me.
- Otherwise reply HEARTBEAT_OK.
  1. Or remove/rename HEARTBEAT.md (if it’s missing, heartbeat runs with the default prompt and the model decides what to do).
#

Then force a test run:

openclaw system event --text "Heartbeat test" --mode now

If you still “see nothing” after that, the next likely gotcha is delivery defaults (target: "none" and showOk: false). We can tune that, but first make HEARTBEAT.md non-empty and re-test.

pastel estuary
#

THIS TIME. some reason it was silent

sharp parcelBOT
#

“Silent” is usually expected behavior once it’s no longer skipping.

Two common reasons you see nothing:

  1. The heartbeat ran and replied HEARTBEAT_OK, and OK acks are suppressed by default (showOk: false).
  2. Your heartbeat is configured to not deliver anywhere (default agents.defaults.heartbeat.target: "none").

Can you paste this?

openclaw system heartbeat last --json
openclaw config get agents.defaults.heartbeat

If you just want a visible proof in this Discord thread, set (temporarily):

#
{
agents: { defaults: { heartbeat: { target: "last" } } },
channels: { discord: { heartbeat: { showOk: true } } },
}

Then force one:

openclaw system event --text "heartbeat delivery test" --mode now

After you confirm it’s working, you can flip showOk back to false so it only pings you when there’s an actual alert.

pastel estuary
sharp parcelBOT
#

Not sussy — that JSON is basically saying:

  • status: "ok-token" → the model replied with HEARTBEAT_OK
  • indicatorType: "ok" → UI indicator can show “heartbeat ok”
  • silent: true → no outbound message was sent (so you won’t see anything in Discord)

That happens when either:

  1. OK acks are still suppressed (showOk isn’t actually true for the effective Discord account), or
  2. your heartbeat has no concrete delivery target. With target: "last", the runner can only send if the heartbeat session has a remembered “last channel/to”. Heartbeats run in the agent main session by default — a Discord guild thread is a different session key — so “last” can be empty.

Make it unambiguously deliver to this Discord thread

Set an explicit destination:

{
agents: {
#
defaults: {
heartbeat: {
every: "30m",
target: "discord",
to: "channel:1481390571242131626", // this thread/channel id
},
},
},
channels: {
discord: {
heartbeat: { showOk: true }, // just for testing
},
},
}
#

Then:

openclaw system event --text "heartbeat delivery test" --mode now

If that posts, you’re done — flip showOk back to false afterward so it only speaks when there’s an actual alert.

If it still goes silent, paste:

openclaw config get channels.discord.heartbeat
openclaw config get channels.defaults.heartbeat
pastel estuary
sharp parcelBOT
#

Yep — just separate run context (“which session does the heartbeat think in?”) from delivery target (“where does it send?”).

Deliver heartbeats to that Telegram DM

Use Telegram as the heartbeat target and set to to the numeric chat id:

{
agents: {
defaults: {
heartbeat: {
target: "telegram",
to: "8508701617",
// accountId: "default", // only if you have multiple Telegram accounts configured
},
},
},
#
}

(Optional) Also run the heartbeat in that session’s context

If you want the heartbeat run to use the history/context of that exact session key:

{
agents: {
defaults: {
heartbeat: {
session: "agent:main:telegram:direct:8508701617",
target: "telegram",
to: "8508701617",
},
#
},
},
}

And if you want “OK” messages to actually show up on Telegram while testing:

{ channels: { telegram: { heartbeat: { showOk: true } } } }

Then force a test:

openclaw system event --text "heartbeat test (telegram)" --mode now