zeroclaw-labs/zeroclaw · error · anyhow::Error

[heartbeat] agent = {agent_alias:?} is not configured ([agen

Error message

[heartbeat] agent = {agent_alias:?} is not configured ([agents.{agent_alias}] missing)

What it means

resolve_heartbeat_workspace_dir looks up config.agent(&agent_alias) to find the [agents.<alias>] entry and its workspace dir. The alias in [heartbeat] agent must match a configured agent exactly; a typo, a renamed agent table, or a missing agent produces this error at heartbeat worker startup.

Source

Thrown at crates/zeroclaw-runtime/src/daemon/mod.rs:1251

            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.
///
/// Hooks receive the resolved agent alias and the pre-computed list of
/// MCP server configs granted to that agent by `mcp_bundles`. They
/// MUST return an `Arc<McpRegistry>` whose inner server lifetime
/// outlives the simulated ticks (returning a fresh registry per call

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Make [heartbeat] agent exactly equal an existing [agents.<alias>] key
  2. Or add the missing [agents.<alias>] table if that agent was supposed to exist
  3. Restart the daemon

Example fix

# before
[heartbeat]
enabled = true
agent = "assist"

[agents.main]
model = "..."

# after
[heartbeat]
enabled = true
agent = "main"

[agents.main]
model = "..."
Defensive patterns

Strategy: validation

Validate before calling

let alias = config.heartbeat.agent.trim();
if config.heartbeat.enabled && config.agent(alias).is_none() {
    anyhow::bail!("[heartbeat] agent = {alias:?} has no matching [agents.{alias}] table");
}

Type guard

fn heartbeat_agent_resolves(config: &zeroclaw_config::schema::Config) -> bool {
    let alias = config.heartbeat.agent.trim();
    !config.heartbeat.enabled || config.agent(alias).is_some()
}

Try / catch

if let Err(err) = run_heartbeat_worker(config.clone()).await {
    if err.to_string().contains("is not configured") {
        eprintln!("config error: [heartbeat] agent alias does not match any [agents.<alias>] key");
    }
    return Err(err);
}

Prevention

When it happens

Trigger: [heartbeat] agent = "assistant" while config only defines [agents.main]; the [agents.*] table was renamed without updating heartbeat; case mismatch such as "Main".

Common situations: Renaming agents during a reorganization and forgetting the heartbeat reference; duplicating config across environments where the agent alias differs.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/b896e82d963b713d. Report an issue: GitHub.