{"record":{"id":"f0cec62f002a4ac1","repo":"zeroclaw-labs/zeroclaw","slug":"unsupported-delivery-channel-other","errorCode":null,"errorMessage":"unsupported delivery channel: {other}","messagePattern":"unsupported delivery channel: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/orchestrator/mod.rs","lineNumber":13054,"sourceCode":"            let peer_resolver: Arc<dyn Fn() -> Vec<String> + Send + Sync> =\n                Arc::new(move || peers.clone());\n            let allowed_groups = wa.allowed_groups.clone();\n            let allowed_groups_resolver: Arc<dyn Fn() -> Vec<String> + Send + Sync> =\n                Arc::new(move || allowed_groups.clone());\n            let ch = WhatsAppWebChannel::new(\n                wa,\n                alias.to_string(),\n                peer_resolver,\n                allowed_groups_resolver,\n            )\n            .with_workspace_dir(config.channel_workspace_dir(&format!(\"whatsapp.{alias}\")));\n            zeroclaw_api::channel::Channel::send(&ch, &make_msg(&safe_output)).await?;\n        }\n        #[cfg(not(feature = \"whatsapp-web\"))]\n        \"whatsapp\" | \"whatsapp-web\" | \"whatsapp_web\" => {\n            anyhow::bail!(\"WhatsApp channel requires the `whatsapp-web` feature\");\n        }\n        other => anyhow::bail!(\"unsupported delivery channel: {other}\"),\n    }\n    #[allow(unreachable_code)]\n    Ok(())\n}\n\n// ── Concurrent persist lock test ─────────────────────────\n// Lives outside `mod tests` so it has direct access to private parent items.\n\n#[cfg(test)]\n#[test]\nfn concurrent_persist_lock_serialization() {\n    use std::sync::Barrier;\n    use std::sync::atomic::{AtomicUsize, Ordering};\n    use std::time::Duration;\n    use zeroclaw_infra::session_backend::SessionBackend;\n    use zeroclaw_providers::ChatMessage;\n    use zeroclaw_runtime::approval::ApprovalManager;\n    use zeroclaw_runtime::observability::NoopObserver;","sourceCodeStart":13036,"sourceCodeEnd":13072,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/orchestrator/mod.rs#L13036-L13072","documentation":"The channel-type prefix (text before the first dot, lowercased) matched no arm in deliver_announcement. The stateless arms cover telegram, discord, slack, signal, wechat, lark/feishu, webhook, wecom_ws, email, and whatsapp; everything else — including typos like \"telegrm\", types with no static send path (e.g. qq, matrix when no live instance is registered), or malformed refs that passed the dotted-ref check — falls into `other` and bails. Live-registered channels are matched earlier via CRON_CHANNEL_REGISTRY, so a normally-supported type can still land here when it is not connected.","triggerScenarios":"deliver_announcement(\"telegra.work\", ...), deliver_announcement(\"qq.main\", ...), or \"matrix.home\" when no live matrix instance is registered; any channel type outside the compiled arm list.","commonSituations":"Typos in cron channel refs; using a channel type that only supports live-registered delivery while it is offline; enum/schema drift where config accepts a type the delivery match was never extended for.","solutions":["Check the type spelling against the supported list and use the exact dotted form <type>.<alias>","If the type is live-only (e.g. matrix), ensure the channel is running and registered so the registry path handles it before the match","If the config schema genuinely accepts a type deliver_announcement does not route, file/extend a match arm rather than working around it"],"exampleFix":"# before\n[cron.report]\nchannel = \"telegra.work\"      # typo -> bail: unsupported delivery channel: telegra\n\n# after\n[cron.report]\nchannel = \"telegram.work\"","handlingStrategy":"type-guard","validationCode":"const STATELESS_DELIVERY_TYPES: &[&str] = &[\n    \"telegram\", \"discord\", \"slack\", \"signal\", \"wechat\", \"lark\", \"feishu\",\n    \"webhook\", \"wecom_ws\", \"wecom-ws\", \"email\", \"whatsapp\", \"whatsapp-web\", \"whatsapp_web\",\n];\n\nfn delivery_type_supported(raw: &str) -> bool {\n    raw.split_once('.')\n        .map(|(kind, _)| STATELESS_DELIVERY_TYPES.contains(&kind.to_ascii_lowercase().as_str()))\n        .unwrap_or(false)\n}","typeGuard":"fn is_supported_delivery_ref(channel: &str) -> bool {\n    let Some((kind, alias)) = channel.split_once('.') else { return false };\n    !alias.is_empty()\n        && STATELESS_DELIVERY_TYPES.contains(&kind.to_ascii_lowercase().as_str())\n        // live-registry types (e.g. matrix) are valid only while connected\n        || matches!(kind.to_ascii_lowercase().as_str(), \"matrix\" if matrix_registered(channel))\n}\n\nassert!(is_supported_delivery_ref(\"telegram.work\"));\nassert!(!is_supported_delivery_ref(\"telegra.work\"));","tryCatchPattern":"match deliver_announcement(&cfg, channel, &target, thread, &out).await {\n    Err(e) if e.to_string().starts_with(\"unsupported delivery channel\") => {\n        // Config typo or a live-only type that is offline: surface to the operator, never retry blindly.\n    }\n    rest => rest?,\n}","preventionTips":["Validate every cron channel string against the supported type list when loading config","Prefer fail-fast config validation over discovering typos at first cron fire","When adding a new channel type to the schema, extend deliver_announcement's match in the same change (tests at orchestrator/mod.rs:33074 assert this invariant)"],"tags":["channels","cron-delivery","routing","typo","validation"],"backgroundTag":"unsupported-channel-type","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}