{"record":{"id":"fe8b17cf4b6b2f7b","repo":"zeroclaw-labs/zeroclaw","slug":"channel-count-channels-stale-stale","errorCode":null,"errorMessage":"{channel_count} channels, {stale} stale","messagePattern":"(.+?) channels, (.+?) stale","errorType":"console","errorClass":"DiagItem","httpStatus":null,"severity":"warning","filePath":"crates/zeroclaw-runtime/src/doctor/mod.rs","lineNumber":1708,"sourceCode":"                .map_or(i64::MAX, |dt| {\n                    Utc::now().signed_duration_since(dt).num_seconds()\n                });\n\n            if status_ok && age <= CHANNEL_STALE_SECONDS {\n                items.push(DiagItem::ok(cat, format!(\"{name} fresh ({age}s ago)\")));\n            } else {\n                stale += 1;\n                items.push(DiagItem::error(\n                    cat,\n                    format!(\"{name} stale (ok={status_ok}, age={age}s)\"),\n                ));\n            }\n        }\n\n        if channel_count == 0 {\n            items.push(DiagItem::warn(cat, \"no channel components tracked yet\"));\n        } else if stale > 0 {\n            items.push(DiagItem::warn(\n                cat,\n                format!(\"{channel_count} channels, {stale} stale\"),\n            ));\n        }\n    }\n}\n\n// ── Environment checks ───────────────────────────────────────────\n\nfn check_environment(items: &mut Vec<DiagItem>) {\n    let cat = \"environment\";\n\n    // git\n    check_command_available(\"git\", &[\"--version\"], cat, items);\n\n    // Shell — Unix uses $SHELL, Windows uses %ComSpec% (path to cmd.exe).\n    let shell = std::env::var(\"SHELL\")\n        .ok()","sourceCodeStart":1690,"sourceCodeEnd":1726,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/doctor/mod.rs#L1690-L1726","documentation":"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`.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Look at the per-channel `stale` error items in the same doctor report to identify which channels are unhealthy.","Check daemon logs (`journalctl --user -u zeroclaw`) for the failing channel's reconnect errors.","Restart the daemon (or the affected channel) to re-establish connections and refresh `last_ok`.","Re-run `zeroclaw doctor` and confirm the stale count drops to zero."],"exampleFix":"# before: doctor shows \"3 channels, 2 stale\" plus per-channel stale errors\n\n# after\njournalctl --user -u zeroclaw -n 200 --no-pager  # find the failing channel\nsystemctl --user restart zeroclaw && sleep 15 && zeroclaw doctor","handlingStrategy":"validation","validationCode":"let now = chrono::Utc::now();\nif let Some(components) = state.get(\"components\").and_then(|c| c.as_object()) {\n    for (name, c) in components {\n        if !name.starts_with(\"channel:\") { continue; }\n        let fresh = c.get(\"last_ok\")\n            .and_then(|v| v.as_str())\n            .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())\n            .map_or(false, |t| (now - t.with_timezone(&chrono::Utc)).num_seconds() <= 300);\n        if !fresh { eprintln!(\"{name} stale\"); }\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Monitor channel `last_ok` ages continuously instead of discovering staleness via doctor.","Alert on reconnect-loop patterns in daemon logs before channels go stale."],"tags":["daemon","channels","health","staleness","doctor","zeroclaw"],"backgroundTag":"stale-heartbeat","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}