zeroclaw-labs/zeroclaw · error · anyhow::Error
heartbeat.to is required when heartbeat.target is set
Error message
heartbeat.to is required when heartbeat.target is set
What it means
resolve_heartbeat_delivery pairs heartbeat.target (the delivery channel) with heartbeat.to (the recipient). Both unset triggers auto-detection of the first configured channel; both set is validated and used; exactly one set is an unaddressable half-specification, so the worker bails at startup.
Source
Thrown at crates/zeroclaw-runtime/src/daemon/mod.rs:2210
.target
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty());
let target = config
.heartbeat
.to
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty());
match (channel, target) {
// Both explicitly set — validate and use.
(Some(channel), Some(target)) => {
validate_heartbeat_channel_config(config, channel)?;
Ok(Some((channel.to_string(), target.to_string())))
}
// Only one set — error.
(Some(_), None) => anyhow::bail!("heartbeat.to is required when heartbeat.target is set"),
(None, Some(_)) => anyhow::bail!("heartbeat.target is required when heartbeat.to is set"),
// Neither set — try auto-detect the first configured channel.
(None, None) => Ok(auto_detect_heartbeat_channel(config)),
}
}
const HEARTBEAT_SESSION_CONTEXT_MESSAGES: usize = 20;
fn load_heartbeat_session_context(config: &Config) -> Option<String> {
use zeroclaw_providers::traits::ChatMessage;
let channel = config
.heartbeat
.target
.as_deref()
.map(str::trim)
.filter(|v| !v.is_empty())?;
let to = configView on GitHub (pinned to 88bb9c8533)
Solutions
- Add to = "<chat id / recipient>" next to target in the [heartbeat] table
- Or remove target (and to) entirely to fall back to auto-detecting the first configured channel
Example fix
# before [heartbeat] enabled = true agent = "main" target = "telegram" # after [heartbeat] enabled = true agent = "main" target = "telegram" to = "123456789"
Defensive patterns
Strategy: validation
Validate before calling
match (&config.heartbeat.target, &config.heartbeat.to) {
(Some(_), None) => anyhow::bail!("heartbeat.to is required when heartbeat.target is set"),
(None, Some(_)) => anyhow::bail!("heartbeat.target is required when heartbeat.to is set"),
_ => {}
} Type guard
fn heartbeat_delivery_pair_is_complete(config: &zeroclaw_config::schema::Config) -> bool {
config.heartbeat.target.is_some() == config.heartbeat.to.is_some()
} Try / catch
if let Err(err) = run_heartbeat_worker(config.clone()).await {
if err.to_string().contains("heartbeat.to is required") {
eprintln!("config error: add heartbeat.to (recipient) alongside heartbeat.target");
}
return Err(err);
} Prevention
- Always set target and to together, or neither — there is no valid half-pair
- Remember both-unset is valid: the first configured channel is auto-detected
When it happens
Trigger: [heartbeat] target = "telegram" with no to key; or the legacy alias channel = "telegram" without a recipient.
Common situations: Adding a target channel to route heartbeat pings but forgetting the chat id; migrating older configs that had channel-only delivery.
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.target is required when heartbeat.to is set
- unsupported heartbeat.target channel: {channel}
- 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/e1cb058418096432.
Report an issue: GitHub.