zeroclaw-labs/zeroclaw · error · anyhow::Error
unsupported heartbeat.target channel: {channel}
Error message
unsupported heartbeat.target channel: {channel} What it means
validate_heartbeat_channel_config checks heartbeat.target against the channel registry via config.channels.is_known_channel (case-insensitive). The registry covers telegram, discord, slack, mattermost, webhook, imessage, matrix, signal, whatsapp, linq, nextcloud_talk, email, gmail_push, irc, twitch, lark, line, dingtalk, wecom, wecom_ws, wechat, qq, twitter, mochat, nostr, clawdtalk, reddit, bluesky, git, voice_call, voice_wake, voice_duplex, mqtt, amqp, and filesystem; anything else is rejected before delivery is attempted.
Source
Thrown at crates/zeroclaw-runtime/src/daemon/mod.rs:2436
}
if !config.channels.discord.is_empty() {
// Discord requires explicit target — can't auto-detect
return None;
}
if !config.channels.slack.is_empty() {
// Slack requires explicit target
return None;
}
if !config.channels.mattermost.is_empty() {
// Mattermost requires explicit target
return None;
}
None
}
fn validate_heartbeat_channel_config(config: &Config, channel: &str) -> Result<()> {
if !config.channels.is_known_channel(channel) {
anyhow::bail!("unsupported heartbeat.target channel: {channel}");
}
if !config.channels.is_channel_configured(channel) {
anyhow::bail!(
"heartbeat.target is set to {channel} but channels.{channel} is not configured"
);
}
if !config.channels.is_channel_deliverable(channel) {
anyhow::bail!(
"heartbeat.target is set to {channel} but {channel} is an input-only channel that cannot deliver outbound messages"
);
}
Ok(())
}
fn has_supervised_channels(config: &Config) -> bool {
config.channels.has_any_enabled()
}
View on GitHub (pinned to 88bb9c8533)
Solutions
- Fix the channel name spelling — matching is case-insensitive, so telegram, Telegram, and TELEGRAM all pass
- Pick a channel that exists in the registry (telegram, discord, slack, matrix, email, webhook, ...)
Example fix
# before [heartbeat] enabled = true agent = "main" target = "telgram" to = "123456789" # after [heartbeat] enabled = true agent = "main" target = "telegram" to = "123456789"
Defensive patterns
Strategy: type-guard
Validate before calling
if let Some(channel) = &config.heartbeat.target {
if !config.channels.is_known_channel(channel) {
anyhow::bail!("unsupported heartbeat.target channel: {channel}");
}
} Type guard
fn is_known_heartbeat_channel(config: &zeroclaw_config::schema::Config, channel: &str) -> bool {
config.channels.is_known_channel(channel)
} Try / catch
if let Err(err) = run_heartbeat_worker(config.clone()).await {
if err.to_string().contains("unsupported heartbeat.target channel") {
eprintln!("config error: heartbeat.target names a channel not in the registry (typo?)");
}
return Err(err);
} Prevention
- Copy channel names from the registry (telegram, discord, slack, matrix, email, ...) rather than typing from memory
- Channel matching is case-insensitive — but the name itself must exist in the registry
When it happens
Trigger: heartbeat.target = "telgram" (typo); using a provider label like "openai" as a channel name; referencing a channel kind not present in this build's registry.
Common situations: Typos in channel names; assuming a plugin/add-on channel is registered; confusing model provider names with channel names.
Understand the failure class
Background: Config validation failed: what "invalid value for {key}" and settings-rejection errors mean across 19 open-source libraries — this error's family across 19 libraries.
Related errors
- heartbeat.to is required when heartbeat.target is set
- heartbeat.target is required when heartbeat.to is set
- heartbeat worker requires `[heartbeat] agent = "<alias>"` na
- heartbeat.target is set to {channel} but channels.{channel}
- heartbeat.target is set to {channel} but {channel} is an inp
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/7265bd312d8e06c5.
Report an issue: GitHub.