#Agent heartbeat messages don't show up in discord

1 messages · Page 1 of 1 (latest)

dense swallow
#

I have 6 agents setup in openclaw. Everything was working correctly in Telegram, but when I migrated to Discord (where I have 1 Discord bot - with bindings to different agents per channel etc), a lot of my heartbeats don't seem to trigger anymore. I tried debugging with openclaw itself, but I never really get anywhere. Happy to provide my openclaw.json below (obfuscated all ids/tokens etc) - I tried to add everything but hit the discord char limit.

I don't see anything specific in openclaw logs that suggests a problem. The only error I've seen today is "[9:52:38 PM] [error] [diagnostic] lane task error: lane=session:agent:main:discord:channel:XXXXX durationMs=317 error="FailoverError: HTTP 401 authentication_error: OAuth token has expired. Please obtain a new token or refresh your existing token. (request_id: req_011CYgovEadNYJ9r7uvFCLoL)"" - but all my models are working correctly on Discord - so not sure if relevant.

Hosted in Proxmox - as an ubuntu 24.04 container locally on my home server. I'm on the latest version - 2026.3.2

gloomy thornBOT
#

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

frigid fableBOT
#

A few “gotchas” here that line up really well with “worked in Telegram, moved to Discord, now a bunch of heartbeats seem gone”:

1) Heartbeats run in the agent’s main session by default (not per Discord channel)

On Discord, each guild channel is a different session key (agent:<agentId>:discord:channel:<channelId>). If you’re mostly chatting in guild channels (and not in Discord DMs), then:

  • the main session might still have last pointing at Telegram (or nothing),
  • so heartbeat.target: "last" can resolve to “not Discord”, and you won’t see anything.

Fix options:

  • Set heartbeat delivery explicitly to Discord with target: "discord" and to: "channel:<id>" (or thread:<id>).
  • Or set heartbeat.session to the Discord channel session key you want the heartbeat to “live in”, then target:"last" will make sense for that session.

Docs: https://docs.openclaw.ai/gateway/heartbeat (see “Delivery behavior” + target/to)

2) If you added any per-agent heartbeat blocks, you may have accidentally disabled the others

This one bites a lot of multi-agent setups:

#

If any agents.list[] entry includes a heartbeat block, only those agents run heartbeats.

So if you have 6 agents but only 2 have heartbeat: {...}, the other 4 will never heartbeat.

Docs: https://docs.openclaw.ai/gateway/heartbeat (see “Per-agent heartbeats”)

3) “Nothing happened” can just mean the model returned HEARTBEAT_OK (and it’s suppressed)

By default, OK acknowledgements are hidden. You’ll only see alerts.

Quick debug move: temporarily enable OK visibility for Discord:

  • channels.discord.heartbeat.showOk: true

Docs: https://docs.openclaw.ai/gateway/heartbeat (see “Visibility controls”)

4) That HTTP 401 … OAuth token has expired error is very likely relevant

Even if “models work” in your active Discord chats, heartbeats might be using:

  • a different agent default model
#
  • a different fallback chain
  • or a different auth profile
    …and the heartbeat run is the one hitting the expired OAuth token.

Run this (safe to paste output; it shouldn’t include secrets):

openclaw models status --probe --agent main
# and for one agent that “stopped heartbeating”:
openclaw models status --probe --agent <agentId>

If it shows an expired OAuth profile, re-auth via:

openclaw models auth login --provider <providerId>

Docs: https://docs.openclaw.ai/cli/models

#

What I’d like you to paste (small + targeted)

These will tell us which of the above is the culprit in ~1 minute:

openclaw config get cron.enabled
openclaw config get agents.defaults.heartbeat
openclaw config get channels.defaults.heartbeat
openclaw config get channels.discord.heartbeat
openclaw config get agents.list

(If agents.list is huge, just paste the parts for each agent that include id and heartbeat.)

Also: are you using Discord DMs at all, or only guild channels? That answer determines whether target:"last" can ever reasonably land on Discord without an explicit to:.

dense swallow
#

openclaw@ubuntu:~$ openclaw config get cron.enabled

🦞 OpenClaw 2026.3.2 (85377a2)
I speak fluent bash, mild sarcasm, and aggressive tab-completion energy.

Config path not found: cron.enabled
openclaw@ubuntu:~$ openclaw config get agents.defaults.heartbeat

🦞 OpenClaw 2026.3.2 (85377a2) — Your config is valid, your assumptions are not.

{
"every": "60m"
}
openclaw@ubuntu:~$ openclaw config get channels.defaults.heartbeat

🦞 OpenClaw 2026.3.2 (85377a2)
I don't just autocomplete—I auto-commit (emotionally), then ask you to review (logically).

Config path not found: channels.defaults.heartbeat
openclaw@ubuntu:~$ openclaw config get channels.discord.heartbeat

🦞 OpenClaw 2026.3.2 (85377a2) — Give me a workspace and I'll give you fewer tabs, fewer toggles, and more oxygen.

Config path not found: channels.discord.heartbeat

#

I've kept only the top few agents - due to discord limits

openclaw@ubuntu:~$ openclaw config get agents.list

🦞 OpenClaw 2026.3.2 (85377a2) — I can't fix your code taste, but I can fix your build and your backlog.

[
{
"id": "main",
"heartbeat": {
"every": "60m",
"activeHours": {
"start": "07:00",
"end": "23:00"
},
"target": "discord",
"to": "channel:1477236369892839475",
"accountId": "default"
}
},
{
"id": "watchdog",
"name": "watchdog",
"workspace": "/home/openclaw/.openclaw/workspace-watchdog",
"agentDir": "/home/openclaw/.openclaw/agents/watchdog/agent",
"model": "minimax-portal/MiniMax-M2.5",
"heartbeat": {
"every": "60m",
"activeHours": {
"start": "07:00",
"end": "23:00"
},
"target": "discord",
"to": "channel:1477237428543684769",
"accountId": "default"
},
"tools": {
"allow": [
"exec",
"process",
"cron",
"message",
"web_search",
"web_fetch"
],
"deny": [
"browser",
"canvas",
"nodes",
"gateway",
"sessions_spawn"
],
"fs": {
"workspaceOnly": false
}
}

frigid fableBOT
#

Your outputs basically explain the whole thing:

  • agents.defaults.heartbeat is set to { every: "60m" }
  • you have no heartbeat.target, and the default target is "none"
    → so heartbeats may run, but they won’t send messages anywhere.

Also, the “Config path not found” responses just mean “you haven’t explicitly set that key in your config file”, not “the feature doesn’t exist”.

Make heartbeats show up in Discord (quick test)

Pick the channel you want the heartbeat to post into and set an explicit delivery target:

openclaw config set agents.defaults.heartbeat.target "discord"
openclaw config set agents.defaults.heartbeat.to "channel:YOUR_DISCORD_CHANNEL_ID"
openclaw config set agents.defaults.heartbeat.every "5m"   # just for testing
openclaw gateway restart
#

Then force an immediate run:

openclaw system event --text "heartbeat test (should post to discord)" --mode now

Notes:

If you want all 6 agents to have heartbeats

There’s another important behavior (surprising, but current):

  • If no agent in agents.list[] has a heartbeat block, OpenClaw only runs heartbeats for the default agent.
  • To enable heartbeats on multiple agents, add a heartbeat block to each of those agents (can be {} and it’ll inherit defaults), or give each agent its own target/to.

Example pattern:

#
{
  agents: {
    defaults: {
      heartbeat: { every: "60m", target: "discord" }
    },
    list: [
      { id: "main", default: true, heartbeat: { to: "channel:AAA" } },
      { id: "ops",  heartbeat: { to: "channel:BBB" } },
      { id: "home", heartbeat: { to: "channel:CCC" } }
    ]
  }
}

About the 401 OAuth token expired

It’s still worth fixing, but it’s separate from “no heartbeat target means no messages”.

#

Run:

openclaw models status --probe --agent main
openclaw models status --probe --agent <one-of-the-agents-that-fails>

Docs: https://docs.openclaw.ai/cli/models

Next thing to paste

Can you paste:

openclaw config get agents.list

I’m looking for whether any of your 6 agents already have a heartbeat block, and what their ids are, so we can wire each one to the right Discord channel cleanly.

#

Nice — this confirms your heartbeat config is already “deliver to Discord channel X” for at least main + watchdog:

heartbeat: {
  every: "60m",
  activeHours: { start: "07:00", end: "23:00" },
  target: "discord",
  to: "channel:…"
}

So if you’re seeing zero heartbeat messages, the remaining usual culprits are:

1) You’re looking in the wrong place (channel vs thread, or different channel ID)

In this help thread, the channel id is 1478501966798913617, but your main heartbeat is configured to post to channel:1477236369892839475 (different ID).

Also: channel:<id> posts into the channel, not a thread.

#

If you want heartbeats inside a thread, set to: "thread:<threadId>".

Quick sanity check: are you watching the exact channel ID that’s in heartbeat.to?

2) Heartbeats are running, but they’re returning HEARTBEAT_OK (and OKs are hidden by default)

Default behavior is “silent OK, only show alerts”. So if your heartbeats are healthy/boring, Discord will show nothing and it feels like they never ran.

To prove it, temporarily enable OK visibility on Discord:

openclaw config set channels.discord.heartbeat '{ showOk: true }'
openclaw gateway restart
openclaw system event --text "heartbeat debug wake" --mode now

That should make the next heartbeat(s) visibly post HEARTBEAT_OK into the configured to: channels.

#

3) Some agents may be skipped because HEARTBEAT.md is “effectively empty”

If a workspace has a HEARTBEAT.md that’s only headers/blank lines, OpenClaw skips the heartbeat run entirely.

For one agent, check:

sed -n '1,80p' /home/openclaw/.openclaw/workspace-watchdog/HEARTBEAT.md

4) The OAuth 401 can still break specific agents

That error is still a real failure (even if other chats “seem fine” due to failover). When you get a minute, paste:

openclaw models status --probe --agent main

#

If you answer just these two, we’ll lock it down fast:

  1. Which Discord destination do you expect heartbeat messages to appear in: a channel or a thread? (and what’s its ID)
  2. After setting channels.discord.heartbeat.showOk: true + triggering the openclaw system event … --mode now, do you see any HEARTBEAT_OK messages anywhere?
dense swallow
#
  1. for the main agent - that would be a channel - 1477236369892839475
  2. yes I got HEARTBEAT_OK from all 6 agents in the correct discord channel
frigid fableBOT
#

Perfect — that means the heartbeat scheduler + routing to Discord is working correctly.

What you were seeing as “heartbeats not triggering” is just the default heartbeat behavior:

  • when the model returns HEARTBEAT_OK, OpenClaw treats that as an ack and suppresses it (so you don’t get spammed every interval)
  • so in normal operation you’ll only see something when the heartbeat produces an actual alert message (no HEARTBEAT_OK)

That’s why forcing OK visibility made them “appear”.

If you want “OK” heartbeats to show in Discord

Set this (and restart gateway):

openclaw config set channels.discord.heartbeat '{ showOk: true }'
openclaw gateway restart
#

Be aware: with 6 agents at every: "60m", that’s up to 6 messages per hour during your activeHours window.

Common tweak if you enable showOk:

  • increase every (e.g. 2h, 4h), or only enable showOk temporarily when debugging.

Docs: https://docs.openclaw.ai/gateway/heartbeat (Visibility controls)

Separate note: the OAuth 401 you saw

That’s still worth fixing (it can cause some runs to fail / rely on failover), but it’s not the reason you weren’t seeing heartbeat messages once everything is returning HEARTBEAT_OK.

If you want, paste:

openclaw models status --probe --agent main

and we can point out exactly which auth profile is expired.

If the showOk setting gives you what you expected, mark the solution message in the thread so it’s indexed.

dense swallow
#

is there a way to share my full openclaw json with you for review?

frigid fableBOT
#

Yes — easiest way is to attach it as a file in this thread (Discord upload), rather than pasting (avoids the char limit).

Two important notes before you share it:

1) Please redact secrets

Before uploading, replace any sensitive values with "REDACTED". In practice that means anything that looks like:

  • ...token...
  • ...apiKey... / ...key...
  • ...secret...
  • ...password...
  • ...cookie...
  • gateway auth tokens / operator tokens

If you’re unsure, it’s totally fine to just redact all values for keys containing those substrings.

Also: don’t share your auth-profiles.json (that’s where OAuth refresh tokens / API keys often live).

#

2) Include the resolved config path (helps a ton)

Run and paste this small output (safe):

openclaw config file

Then upload the redacted config file it points to.

If you want a “safe-to-share” snapshot instead

This is often enough for review and already redacts tokens:

openclaw status --all

If you upload either (or both), tell me what you want reviewed (heartbeat delivery, Discord routing/bindings, models/auth/failover, etc.) and I’ll focus on the relevant bits.

dense swallow
#

File uploads are disabled i think ?

And my commands output is triggering discord char limit

frigid fableBOT
#

Yeah, some Discord servers disable attachments, and the 2k char limit makes “paste the whole config” painful.

You’ve got a few workable options:

Option A (best): post it via an unlisted/private paste link

Use something like a secret GitHub gist (or any paste service you trust), and drop the link here.

Before you paste/upload: redact anything that looks like token, apiKey, secret, password, cookies, etc. (If it’s a credential, assume it’s sensitive.)

Option B: share just the relevant slices (stays under the limit)

For review, I usually only need these parts:

  1. Heartbeat + agent routing:
openclaw config get agents.list | jq 'map({id, name, model, heartbeat, workspace, agentDir})'
#
  1. Discord config (redact tokens manually if they appear):
openclaw config get channels.discord
  1. The OAuth/credential issue you saw (this one is usually short enough with --plain):
openclaw models status --probe --agent main --plain

If you don’t have jq, tell me and I’ll give a no-jq version.

Option C: split across multiple Discord messages

Run the command, then paste in separate chunks (e.g. “part 1/4”, “part 2/4”). Not fun, but works without external links.


#

Also: since we already proved heartbeats are firing (you got HEARTBEAT_OK from all 6), the main thing worth reviewing now is that OAuth token has expired lane error + your model/auth config. If you only paste one thing next, make it:

openclaw models status --probe --agent main --plain
frigid fableBOT
#

Pulled up the gist — config looks sane, and it matches what we just observed in practice.

Heartbeats: nothing “broken”, just suppressed OKs

  • All your heartbeat-enabled agents are configured to deliver to Discord channels (target: "discord", to: "channel:…") during 07:00–23:00.
  • By default, Discord won’t show anything when the model returns HEARTBEAT_OK (so it feels like nothing ran).
  • You proved the whole pipeline is fine when you enabled showOk.

If you want that permanently:

channels: { discord: { heartbeat: { showOk: true } } }

Just keep in mind that’s up to 6 “OK” messages per hour with your current intervals.

One important gotcha in your agent list

You have explicit per-agent heartbeats, so only agents that have a heartbeat block will run heartbeats.

In your gist, worky has no heartbeat block — so it will not heartbeat unless you add one. (That likely explains why you saw “6 agents” heartbeating: it’s everyone except worky.)

#

The OAuth 401 is very likely coming from your MiniMax auth

Your config shows:

  • auth.profiles["minimax-portal:default"].mode = "oauth"

That lines up perfectly with: OAuth token has expired.

Even if chat “works” due to fallbacks, you’ll keep seeing noisy lane errors until you refresh that login.

Next step I’d do:

openclaw models status --probe --agent main --plain

…and then re-auth whatever provider it flags as expired (very likely minimax-portal), e.g.:

openclaw models auth login --provider minimax-portal
#

Small cleanup (optional, but reduces confusion)

  • You have Discord token set both at channels.discord.token and channels.discord.accounts.default.token. You only need one. (Not harmful, just easy to get out of sync.)
  • channels.discord.streaming is partial globally, but off in the default account → effective streaming is off for the default account.

If you paste the openclaw models status --probe --agent main --plain output (or gist it), I can tell you exactly which profile is expired and what the cleanest fix is for your setup.

dense swallow
#

openclaw@ubuntu:~$ openclaw models status --probe --agent main --plain

🦞 OpenClaw 2026.3.2 (85377a2)
Shell yeah—I'm here to pinch the toil and leave you the glory.

[openclaw] Failed to start CLI: Error: --probe cannot be used with --plain output.
at modelsStatusCommand (file:///home/openclaw/.npm-global/lib/node_modules/openclaw/dist/models-b8f546MT.js:1337:38)
at Object.run (file:///home/openclaw/.npm-global/lib/node_modules/openclaw/dist/run-main-B2OIJrIW.js:328:10)
at async runCli (file:///home/openclaw/.npm-global/lib/node_modules/openclaw/dist/run-main-B2OIJrIW.js:409:6)
openclaw@ubuntu:~

frigid fableBOT
#

Yep — that error is just the CLI being strict: --probe can’t be combined with --plain.

Use one of these instead:

Option 1 (simplest): probe with the normal output

openclaw models status --probe --agent main

If it’s too long for Discord, gist it like you did with the config.

Option 2 (shorter): probe only the likely-broken profile/provider

Given your config (minimax-portal:default is mode: "oauth"), this is the prime suspect:

openclaw models status --probe --agent main --probe-provider minimax-portal
# or, if supported in your build:
#
openclaw models status --probe --agent main --probe-profile minimax-portal:default

Option 3 (compact + pasteable): JSON + filter

If you have jq:

openclaw models status --probe --agent main --json | jq .

(Then you can narrow it further once we see the JSON shape.)


If you don’t want to wait on the probe: since you’re already seeing OAuth token has expired, you can usually fix it directly by re-authing that provider:

#
openclaw models auth login --provider minimax-portal

Run Option 2 (probe minimax-portal only) and paste/gist the output — that should confirm it immediately.

frigid fableBOT
#

Got it — that probe output is super clear. Your original lane error is coming from Anthropic, not Discord/heartbeats:

What’s actually expired

In the Auth probes table:

  • anthropic/claude-opus-4-6 using profile anthropic:pro_plan (token) → HTTP 401
    authentication_error: OAuth token has expired…

And above that it says:

  • anthropic:pro_plan static

That sk-ant-o… style token is a Claude setup-token (subscription token). Those are static and do expire, and there’s nothing for OpenClaw to “refresh” automatically — you need to paste a new one (or switch to an API key).

This also explains why “things still work”: agents that try Anthropic first will fail it, then fail over to your other providers/models.

Fix options (pick one)

#

Option A: Refresh the Anthropic setup-token (keep the same workflow)

  1. Generate a new setup-token on any machine where you have Claude CLI logged in:
    • claude setup-token
  2. Paste it into OpenClaw:
    openclaw models auth setup-token
    

Docs: https://docs.openclaw.ai/cli/models (Auth profiles section)

Option B: Switch to an Anthropic API key (more stable)

Use openclaw models auth add (or set ANTHROPIC_API_KEY and re-run auth setup) so Anthropic is API-key based instead of setup-token based.

Side note: MiniMax probe timed out

minimax-portal/MiniMax-M2.5 probe timed out (10.8s). If you don’t see issues in real usage, it’s probably just the probe timeout being a bit tight. If you want to confirm, re-run with a larger timeout: