{"record":{"id":"5e85e61a5bf93ca7","repo":"zeroclaw-labs/zeroclaw","slug":"heartbeat-worker-requires-heartbeat-agent-a","errorCode":null,"errorMessage":"heartbeat worker requires `[heartbeat] agent = \"<alias>\"` naming a configured agent","messagePattern":"heartbeat worker requires `\\[heartbeat\\] agent = \"<alias>\"` naming a configured agent","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/daemon/mod.rs","lineNumber":1246,"sourceCode":"                    if ran_for >= stable_run {\n                        backoff = initial_backoff_secs.max(1);\n                    }\n                }\n            }\n\n            crate::health::bump_component_restart(name);\n            crate::util::release_freed_heap();\n            tokio::time::sleep(Duration::from_secs(backoff)).await;\n            // Double backoff AFTER sleeping so first error uses initial_backoff\n            backoff = backoff.saturating_mul(2).min(max_backoff);\n        }\n    })\n}\n\nfn resolve_heartbeat_workspace_dir(config: &Config) -> Result<(String, PathBuf)> {\n    let agent_alias = config.heartbeat.agent.trim().to_string();\n    if agent_alias.is_empty() {\n        anyhow::bail!(\n            \"heartbeat worker requires `[heartbeat] agent = \\\"<alias>\\\"` naming a configured agent\"\n        );\n    }\n    if config.agent(&agent_alias).is_none() {\n        anyhow::bail!(\n            \"[heartbeat] agent = {agent_alias:?} is not configured ([agents.{agent_alias}] missing)\"\n        );\n    }\n    let workspace_dir = config.agent_workspace_dir(&agent_alias);\n    Ok((agent_alias, workspace_dir))\n}\n\n/// Test-only hook for [`connect_heartbeat_mcp_registry`]. The daemon\n/// heartbeat worker builds an `Arc<McpRegistry>` once at worker start\n/// and shares it across every tick so that stdio MCP children live\n/// for the daemon's lifetime. Tests inject a hook here to\n/// count invocations and assert the registry is constructed at most\n/// once for N simulated ticks.","sourceCodeStart":1228,"sourceCodeEnd":1264,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/daemon/mod.rs#L1228-L1264","documentation":"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.","triggerScenarios":"[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.","commonSituations":"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.","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"],"exampleFix":"# zeroclaw.toml — before\n[heartbeat]\nenabled = true\ninterval_minutes = 30\n\n# after\n[heartbeat]\nenabled = true\nagent = \"main\"\ninterval_minutes = 30\n\n# and the referenced agent must exist\n[agents.main]\nmodel = \"...\"","handlingStrategy":"validation","validationCode":"let alias = config.heartbeat.agent.trim();\nif config.heartbeat.enabled && alias.is_empty() {\n    anyhow::bail!(\"[heartbeat] agent is required when heartbeat is enabled\");\n}","typeGuard":"fn heartbeat_agent_is_set(config: &zeroclaw_config::schema::Config) -> bool {\n    !config.heartbeat.enabled || !config.heartbeat.agent.trim().is_empty()\n}","tryCatchPattern":"if let Err(err) = run_heartbeat_worker(config.clone()).await {\n    if err.to_string().contains(\"[heartbeat] agent\") {\n        eprintln!(\"config error: set [heartbeat] agent = \\\"<alias>\\\" naming an [agents.<alias>] entry\");\n    }\n    return Err(err);\n}","preventionTips":["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"],"tags":["heartbeat","config-validation","agents"],"backgroundTag":"missing-config-value","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}