{"record":{"id":"ec3df09b2594d60e","repo":"zeroclaw-labs/zeroclaw","slug":"wecom-ws-channel-is-not-connected","errorCode":null,"errorMessage":"wecom_ws channel is not connected","messagePattern":"wecom_ws channel is not connected","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/orchestrator/mod.rs","lineNumber":13004,"sourceCode":"                wh.auth_header.clone(),\n                wh.secret.clone(),\n                wh.max_retries,\n                wh.retry_base_delay_ms,\n                wh.retry_max_delay_ms,\n            );\n            zeroclaw_api::channel::Channel::send(&ch, &make_msg(&safe_output)).await?;\n        }\n        #[cfg(not(feature = \"channel-webhook\"))]\n        \"webhook\" => {\n            anyhow::bail!(\"Webhook channel requires the `channel-webhook` feature\");\n        }\n        \"wecom_ws\" | \"wecom-ws\" => {\n            let _ = config\n                .channels\n                .wecom_ws\n                .get(alias)\n                .ok_or_else(not_configured)?;\n            anyhow::bail!(\"wecom_ws channel is not connected\");\n        }\n        #[cfg(feature = \"channel-email\")]\n        \"email\" => {\n            let em = config\n                .channels\n                .email\n                .get(alias)\n                .ok_or_else(not_configured)?;\n            let peers = config.channel_external_peers(\"email\", alias);\n            let peer_resolver: Arc<dyn Fn() -> Vec<String> + Send + Sync> =\n                Arc::new(move || peers.clone());\n            let ch = EmailChannel::new(em.clone(), alias.to_string(), peer_resolver);\n            zeroclaw_api::channel::Channel::send(&ch, &make_msg(&safe_output)).await?;\n        }\n        #[cfg(not(feature = \"channel-email\"))]\n        \"email\" => {\n            anyhow::bail!(\"Email channel requires the `channel-email` feature\");\n        }","sourceCodeStart":12986,"sourceCodeEnd":13022,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/orchestrator/mod.rs#L12986-L13022","documentation":"The wecom_ws arm of deliver_announcement has no stateless send path: it first confirms [channels.wecom_ws.<alias>] exists (else the separate \"not configured\" error) and then always bails with \"wecom_ws channel is not connected\". WeCom WebSocket delivery only works through a live connected instance, normally looked up earlier in CRON_CHANNEL_REGISTRY; reaching this bail means the alias is configured but no live connection was registered at delivery time.","triggerScenarios":"Calling deliver_announcement with channel = \"wecom_ws.<alias>\" (or \"wecom-ws.<alias>\") while the WeCom WebSocket channel is disabled, not yet started, or has dropped its connection; delivery before startup finished connecting.","commonSituations":"Cron firing during startup before the wecom_ws channel registered; the channel crashed or was disabled in config while its cron entries remained; using wecom_ws crons on a deployment that never runs the WeCom listener.","solutions":["Enable and start the wecom_ws channel so the orchestrator connects it at startup and registers it in CRON_CHANNEL_REGISTRY (registry hits return before this arm is reached)","Check channel connection logs/state at delivery time and requeue the delivery until connected","If you only need push delivery without a live socket, route the cron through the webhook channel instead"],"exampleFix":"# before\n[channels.wecom_ws.bot]\ncorp_id = \"...\"   # configured, but channel disabled / not connected\n[cron.alert]\nchannel = \"wecom_ws.bot\"   # -> bail: not connected\n\n# after\n[channels]\nenable = [\"wecom_ws\"]      # channel starts, registers, cron delivers via live instance","handlingStrategy":"validation","validationCode":"// Before a wecom_ws delivery, confirm the channel is actually up:\n// (outside the crate, approximate with your own connection tracker)\nif channel.starts_with(\"wecom_ws.\") && !wecom_connected(alias) {\n    // defer the delivery instead of attempting it\n    return schedule_retry_in(Duration::from_secs(60)).await;\n}\n// inside zeroclaw-channels, the canonical check is the registry:\n// CRON_CHANNEL_REGISTRY.read().unwrap_or_else(|e| e.into_inner()).contains_key(&channel.to_ascii_lowercase())","typeGuard":null,"tryCatchPattern":"match deliver_announcement(&cfg, \"wecom_ws.bot\", &target, thread, &out).await {\n    Err(e) if e.to_string().ends_with(\"wecom_ws channel is not connected\") => {\n        // Transient availability problem: requeue/defer this run; alert if it persists.\n    }\n    rest => rest?,\n}","preventionTips":["Start wecom_ws channels at daemon startup so the registry path handles delivery before the static arm","Monitor channel connection state and requeue wecom_ws crons while disconnected","Prefer the webhook channel for push-style crons when no live WeCom socket is guaranteed"],"tags":["wecom","wecom-ws","websocket","connection","cron-delivery","channels"],"backgroundTag":"channel-not-connected","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}