#heartbeat refuses to work
1 messages · Page 1 of 1 (latest)
Heartbeat “not working” is very often just “it’s running, but it’s intentionally silent”.
Two key defaults:
agents.defaults.heartbeat.targetdefaults to"none"→ it runs but doesn’t deliver anywhereHEARTBEAT_OKacks 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(yourHEARTBEAT.mdis “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:
openclaw system event --text "Heartbeat test ping" --mode now
If that works, heartbeat is fine — you can turn showOk back off afterward to avoid spam.
Docs (worth skimming):
Paste those 4 command outputs and we’ll pinpoint which case you’re in.
ah. i see.
"skipped"
reason: "empty heartbeat file"
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:
- 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.
- 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.
THIS TIME. some reason it was silent
“Silent” is usually expected behavior once it’s no longer skipping.
Two common reasons you see nothing:
- The heartbeat ran and replied
HEARTBEAT_OK, and OK acks are suppressed by default (showOk: false). - 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.
{
"ts": 1773263786311,
"status": "ok-token",
"reason": "interval",
"durationMs": 9337,
"silent": true,
"indicatorType": "ok"
}
sussy...
{
"every": "30m",
"model": "gle-internal/gemini-3.1-pro-preview",
"target": "last",
"directPolicy": "allow",
"prompt": "Read HEARTBEAT.md."
}
i use gle-internal bc i work there + we got NO RATELIMITS EVAHH BABYE
Not sussy — that JSON is basically saying:
status: "ok-token"→ the model replied withHEARTBEAT_OKindicatorType: "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:
- OK acks are still suppressed (
showOkisn’t actually true for the effective Discord account), or - 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
okay, what if i want it to go to... agent:main:telegram:direct:8508701617, for example?
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