zeroclaw-labs/zeroclaw · error · anyhow::Error
heartbeat worker requires `[heartbeat] agent = "<alias>"` na
Error message
heartbeat worker requires `[heartbeat] agent = "<alias>"` naming a configured agent
What it means
resolve_heartbeat_workspace_dir resolves which agent workspace the heartbeat worker runs in. HeartbeatConfig.agent defaults to an empty string and there is deliberately no default-agent fallback, so enabling the heartbeat without [heartbeat] agent = "<alias>" fails fast at worker startup instead of pinging from an unintended workspace.
Source
Thrown at crates/zeroclaw-runtime/src/daemon/mod.rs:1246
if ran_for >= stable_run {
backoff = initial_backoff_secs.max(1);
}
}
}
crate::health::bump_component_restart(name);
crate::util::release_freed_heap();
tokio::time::sleep(Duration::from_secs(backoff)).await;
// Double backoff AFTER sleeping so first error uses initial_backoff
backoff = backoff.saturating_mul(2).min(max_backoff);
}
})
}
fn resolve_heartbeat_workspace_dir(config: &Config) -> Result<(String, PathBuf)> {
let agent_alias = config.heartbeat.agent.trim().to_string();
if agent_alias.is_empty() {
anyhow::bail!(
"heartbeat worker requires `[heartbeat] agent = \"<alias>\"` naming a configured agent"
);
}
if config.agent(&agent_alias).is_none() {
anyhow::bail!(
"[heartbeat] agent = {agent_alias:?} is not configured ([agents.{agent_alias}] missing)"
);
}
let workspace_dir = config.agent_workspace_dir(&agent_alias);
Ok((agent_alias, workspace_dir))
}
/// Test-only hook for [`connect_heartbeat_mcp_registry`]. The daemon
/// heartbeat worker builds an `Arc<McpRegistry>` once at worker start
/// and shares it across every tick so that stdio MCP children live
/// for the daemon's lifetime. Tests inject a hook here to
/// count invocations and assert the registry is constructed at most
/// once for N simulated ticks.View on GitHub (pinned to 88bb9c8533)
Solutions
- Set [heartbeat] agent = "<alias>" where <alias> matches an existing [agents.<alias>] table
- Verify the referenced agent exists: check the [agents.<alias>] section in zeroclaw.toml
- Restart the daemon so the heartbeat worker starts cleanly
Example fix
# zeroclaw.toml — before [heartbeat] enabled = true interval_minutes = 30 # after [heartbeat] enabled = true agent = "main" interval_minutes = 30 # and the referenced agent must exist [agents.main] model = "..."
Defensive patterns
Strategy: validation
Validate before calling
let alias = config.heartbeat.agent.trim();
if config.heartbeat.enabled && alias.is_empty() {
anyhow::bail!("[heartbeat] agent is required when heartbeat is enabled");
} Type guard
fn heartbeat_agent_is_set(config: &zeroclaw_config::schema::Config) -> bool {
!config.heartbeat.enabled || !config.heartbeat.agent.trim().is_empty()
} Try / catch
if let Err(err) = run_heartbeat_worker(config.clone()).await {
if err.to_string().contains("[heartbeat] agent") {
eprintln!("config error: set [heartbeat] agent = \"<alias>\" naming an [agents.<alias>] entry");
}
return Err(err);
} Prevention
- Treat [heartbeat] enabled = true and agent = "..." as a mandatory pair in config review
- After renaming [agents.*] tables, grep zeroclaw.toml for stale alias references including heartbeat
When it happens
Trigger: [heartbeat] enabled = true with no agent key, agent = "", or agent = " " (trimmed empty). Raised when the daemon spawns run_heartbeat_worker at startup or in the heartbeat workspace tests.
Common situations: Copying a minimal heartbeat example that omits agent; re-enabling heartbeat after agent tables were renamed; upgrading from a setup that assumed an implicit default agent.
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}
- ACP request_permission failed: {} ({})
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/5e85e61a5bf93ca7.
Report an issue: GitHub.