zeroclaw-labs/zeroclaw · error · anyhow::Error
heartbeat.target is set to {channel} but channels.{channel}
Error message
heartbeat.target is set to {channel} but channels.{channel} is not configured What it means
The heartbeat target passes is_known_channel but is_channel_configured returns false: the channel kind exists in the registry yet no [channels.<name>] instance is present in the loaded config. Delivery needs a configured channel to send through, so validation fails before the worker starts.
Source
Thrown at crates/zeroclaw-runtime/src/daemon/mod.rs:2439
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()
}
// run_mqtt_sop_listener has been moved to zeroclaw-channels::orchestrator::mqtt.
// The daemon now receives it as a starter via DaemonRegistry::register_mqtt.
View on GitHub (pinned to 88bb9c8533)
Solutions
- Add a [channels.<name>] configuration block (token, chat id, etc.) to the config the daemon actually loads
- Or point heartbeat.target at a channel that is already configured
- Confirm which config file the daemon resolves (data dir / config path) before editing
Example fix
# before [heartbeat] enabled = true agent = "main" target = "telegram" to = "123456789" # no [channels.telegram] anywhere # after [heartbeat] enabled = true agent = "main" target = "telegram" to = "123456789" [channels.telegram] bot_token = "..."
Defensive patterns
Strategy: validation
Validate before calling
if let Some(channel) = &config.heartbeat.target {
if !config.channels.is_channel_configured(channel) {
anyhow::bail!("heartbeat.target = {channel} but channels.{channel} is not configured");
}
} Type guard
fn heartbeat_channel_is_configured(config: &zeroclaw_config::schema::Config) -> bool {
match config.heartbeat.target.as_deref() {
Some(channel) => config.channels.is_channel_configured(channel),
None => true,
}
} Try / catch
if let Err(err) = run_heartbeat_worker(config.clone()).await {
if err.to_string().contains("is not configured") {
eprintln!("config error: add a [channels.<name>] block for the heartbeat target channel");
}
return Err(err);
} Prevention
- Verify the daemon loads the same config file you edited (config path vs data dir)
- In env-driven deployments, confirm channel credential env vars are present in the daemon's environment
When it happens
Trigger: heartbeat.target = "telegram" while [channels.telegram] is absent or empty; the daemon loading a different zeroclaw.toml/profile than the one edited; channel credentials supplied via environment variables that are not set in the daemon's environment.
Common situations: Config split across multiple files or profiles; env-driven deployments where the daemon misses the channel env vars; copying heartbeat config to a fresh install without the channel block.
Related errors
- [heartbeat] agent = {agent_alias:?} is not configured ([agen
- 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 {channel} is an inp
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/4f3cc9135d82b87b.
Report an issue: GitHub.