zeroclaw-labs/zeroclaw · warning · DiagItem

{channel_count} channels, {stale} stale

Error message

{channel_count} channels, {stale} stale

What it means

A `zeroclaw doctor` warning from `check_daemon_state`: a summary line emitted when at least one tracked channel component is stale. Each stale channel already produced its own error item (`<name> stale (ok=..., age=...)`); this warning aggregates the count (`{channel_count} channels, {stale} stale`) so the overall channel health is visible at a glance. Stale means status not `ok` or `last_ok` older than `CHANNEL_STALE_SECONDS`.

Source

Thrown at crates/zeroclaw-runtime/src/doctor/mod.rs:1708

                .map_or(i64::MAX, |dt| {
                    Utc::now().signed_duration_since(dt).num_seconds()
                });

            if status_ok && age <= CHANNEL_STALE_SECONDS {
                items.push(DiagItem::ok(cat, format!("{name} fresh ({age}s ago)")));
            } else {
                stale += 1;
                items.push(DiagItem::error(
                    cat,
                    format!("{name} stale (ok={status_ok}, age={age}s)"),
                ));
            }
        }

        if channel_count == 0 {
            items.push(DiagItem::warn(cat, "no channel components tracked yet"));
        } else if stale > 0 {
            items.push(DiagItem::warn(
                cat,
                format!("{channel_count} channels, {stale} stale"),
            ));
        }
    }
}

// ── Environment checks ───────────────────────────────────────────

fn check_environment(items: &mut Vec<DiagItem>) {
    let cat = "environment";

    // git
    check_command_available("git", &["--version"], cat, items);

    // Shell — Unix uses $SHELL, Windows uses %ComSpec% (path to cmd.exe).
    let shell = std::env::var("SHELL")
        .ok()

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Look at the per-channel `stale` error items in the same doctor report to identify which channels are unhealthy.
  2. Check daemon logs (`journalctl --user -u zeroclaw`) for the failing channel's reconnect errors.
  3. Restart the daemon (or the affected channel) to re-establish connections and refresh `last_ok`.
  4. Re-run `zeroclaw doctor` and confirm the stale count drops to zero.

Example fix

# before: doctor shows "3 channels, 2 stale" plus per-channel stale errors

# after
journalctl --user -u zeroclaw -n 200 --no-pager  # find the failing channel
systemctl --user restart zeroclaw && sleep 15 && zeroclaw doctor
Defensive patterns

Strategy: validation

Validate before calling

let now = chrono::Utc::now();
if let Some(components) = state.get("components").and_then(|c| c.as_object()) {
    for (name, c) in components {
        if !name.starts_with("channel:") { continue; }
        let fresh = c.get("last_ok")
            .and_then(|v| v.as_str())
            .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
            .map_or(false, |t| (now - t.with_timezone(&chrono::Utc)).num_seconds() <= 300);
        if !fresh { eprintln!("{name} stale"); }
    }
}

Prevention

When it happens

Trigger: Running `zeroclaw doctor` when one or more `channel:*` components in the daemon state file report a non-ok status or a `last_ok` timestamp older than the channel staleness threshold — e.g. a Telegram connection dropped, or the daemon suspended and resumed.

Common situations: Network outage killing long-poll connections; expired/revoked bot token causing reconnect loops; laptop resumed from suspend leaving timestamps old; daemon overloaded so heartbeats lag.

Related errors


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