zeroclaw-labs/zeroclaw · error · anyhow::Error
heartbeat.target is set to {channel} but {channel} is an inp
Error message
heartbeat.target is set to {channel} but {channel} is an input-only channel that cannot deliver outbound messages What it means
is_channel_deliverable is false for input-only transports — voice_wake, voice_duplex, mqtt, amqp, and filesystem — whose Channel::send is a no-op. Selecting one as heartbeat.target would silently drop every heartbeat ping, so validate_heartbeat_channel_config rejects it even when the channel is known and configured.
Source
Thrown at crates/zeroclaw-runtime/src/daemon/mod.rs:2444
}
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()
}
// run_mqtt_sop_listener has been moved to zeroclaw-channels::orchestrator::mqtt.
// The daemon now receives it as a starter via DaemonRegistry::register_mqtt.
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
use zeroclaw_config::schema::MattermostListenMode;View on GitHub (pinned to 88bb9c8533)
Solutions
- Choose a deliverable outbound channel (telegram, discord, slack, matrix, email, webhook, ...)
- If you need the heartbeat in an input-only transport, deliver via a deliverable channel to a webhook bridge that republishes it
Example fix
# before [heartbeat] enabled = true agent = "main" target = "mqtt" to = "home/alerts" # 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_channel_deliverable(channel) {
anyhow::bail!("heartbeat.target = {channel} is input-only and cannot deliver outbound messages");
}
} Type guard
fn is_deliverable_heartbeat_channel(config: &zeroclaw_config::schema::Config, channel: &str) -> bool {
config.channels.is_channel_deliverable(channel)
} Try / catch
if let Err(err) = run_heartbeat_worker(config.clone()).await {
if err.to_string().contains("input-only channel") {
eprintln!("config error: pick a deliverable channel (telegram, discord, slack, email, ...) for heartbeat.target");
}
return Err(err);
} Prevention
- Remember voice_wake, voice_duplex, mqtt, amqp, and filesystem cannot send — their send is a no-op
- Bridge instead: deliver to a webhook that republishes into the input-only transport
When it happens
Trigger: heartbeat.target = "mqtt", "amqp", "voice_wake", "voice_duplex", or "filesystem" with the target/to pair set (the channel may even be fully configured).
Common situations: Home-automation setups that mostly receive over MQTT being reused as a heartbeat sink; picking filesystem intending heartbeats to be written as files.
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
- unsupported heartbeat.target channel: {channel}
- heartbeat.target is set to {channel} but channels.{channel}
- unsupported room visibility '{other}': expected private or p
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/8ebbc32762c65024.
Report an issue: GitHub.