{"record":{"id":"4f3cc9135d82b87b","repo":"zeroclaw-labs/zeroclaw","slug":"heartbeat-target-is-set-to-channel-but-channels","errorCode":null,"errorMessage":"heartbeat.target is set to {channel} but channels.{channel} is not configured","messagePattern":"heartbeat\\.target is set to (.+?) but channels\\.(.+?) is not configured","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/daemon/mod.rs","lineNumber":2439,"sourceCode":"        return None;\n    }\n    if !config.channels.slack.is_empty() {\n        // Slack requires explicit target\n        return None;\n    }\n    if !config.channels.mattermost.is_empty() {\n        // Mattermost requires explicit target\n        return None;\n    }\n    None\n}\n\nfn validate_heartbeat_channel_config(config: &Config, channel: &str) -> Result<()> {\n    if !config.channels.is_known_channel(channel) {\n        anyhow::bail!(\"unsupported heartbeat.target channel: {channel}\");\n    }\n    if !config.channels.is_channel_configured(channel) {\n        anyhow::bail!(\n            \"heartbeat.target is set to {channel} but channels.{channel} is not configured\"\n        );\n    }\n    if !config.channels.is_channel_deliverable(channel) {\n        anyhow::bail!(\n            \"heartbeat.target is set to {channel} but {channel} is an input-only channel that cannot deliver outbound messages\"\n        );\n    }\n    Ok(())\n}\n\nfn has_supervised_channels(config: &Config) -> bool {\n    config.channels.has_any_enabled()\n}\n\n// run_mqtt_sop_listener has been moved to zeroclaw-channels::orchestrator::mqtt.\n// The daemon now receives it as a starter via DaemonRegistry::register_mqtt.\n","sourceCodeStart":2421,"sourceCodeEnd":2457,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/daemon/mod.rs#L2421-L2457","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\n[heartbeat]\nenabled = true\nagent = \"main\"\ntarget = \"telegram\"\nto = \"123456789\"\n# no [channels.telegram] anywhere\n\n# after\n[heartbeat]\nenabled = true\nagent = \"main\"\ntarget = \"telegram\"\nto = \"123456789\"\n\n[channels.telegram]\nbot_token = \"...\"","handlingStrategy":"validation","validationCode":"if let Some(channel) = &config.heartbeat.target {\n    if !config.channels.is_channel_configured(channel) {\n        anyhow::bail!(\"heartbeat.target = {channel} but channels.{channel} is not configured\");\n    }\n}","typeGuard":"fn heartbeat_channel_is_configured(config: &zeroclaw_config::schema::Config) -> bool {\n    match config.heartbeat.target.as_deref() {\n        Some(channel) => config.channels.is_channel_configured(channel),\n        None => true,\n    }\n}","tryCatchPattern":"if let Err(err) = run_heartbeat_worker(config.clone()).await {\n    if err.to_string().contains(\"is not configured\") {\n        eprintln!(\"config error: add a [channels.<name>] block for the heartbeat target channel\");\n    }\n    return Err(err);\n}","preventionTips":["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"],"tags":["heartbeat","channels","config-reference"],"backgroundTag":"unresolved-config-reference","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}