zeroclaw-labs/zeroclaw · error
WeCom: invalid scope format: {scope}
Error message
WeCom: invalid scope format: {scope} What it means
WeCom WS destinations are addressed by a scope string; parse_scope accepts exactly 'user--<userid>' (direct message, type 1) or 'group--<chatid>' (group chat, type 2) — note the double dash. Any other shape bails with the offending scope verbatim.
Source
Thrown at crates/zeroclaw-channels/src/wecom_ws.rs:2144
"jpg"
} else if bytes.starts_with(b"GIF87a") || bytes.starts_with(b"GIF89a") {
"gif"
} else if bytes.len() >= 12 && &bytes[..4] == b"RIFF" && &bytes[8..12] == b"WEBP" {
"webp"
} else {
"bin"
}
}
/// Parse scope string into (chat_type, chatid) for aibot_send_msg.
/// `user--{userid}` → (1, userid), `group--{chatid}` → (2, chatid)
fn parse_scope(scope: &str) -> Result<(u32, &str)> {
if let Some(userid) = scope.strip_prefix("user--") {
Ok((1, userid))
} else if let Some(chatid) = scope.strip_prefix("group--") {
Ok((2, chatid))
} else {
anyhow::bail!("WeCom: invalid scope format: {scope}")
}
}
fn summarize_attachment_url_for_log(url: &str) -> String {
let trimmed = url.trim();
if trimmed.is_empty() {
return "empty-url".to_string();
}
match reqwest::Url::parse(trimmed) {
Ok(parsed) => {
let host = parsed.host_str().unwrap_or("unknown-host");
let query_state = if parsed.query().is_some() {
"query=present"
} else {
"query=none"
};
format!(
"{}://{}{} ({query_state})",View on GitHub (pinned to 88bb9c8533)
Solutions
- Format scopes as 'user--<userid>' for direct and 'group--<chatid>' for groups — double dash, no spaces
- Derive scope strings from inbound messages (echo the reply scope) instead of hand-building them
- Validate/normalize scopes at the config or data boundary before they reach the channel
Example fix
// before
let scope = format!("user-{userid}");
// after
let scope = format!("user--{userid}"); Defensive patterns
Strategy: validation
Validate before calling
fn is_valid_wecom_scope(scope: &str) -> bool {
let id = scope
.strip_prefix("user--")
.or_else(|| scope.strip_prefix("group--"));
id.is_some_and(|id| !id.is_empty())
}
assert!(
is_valid_wecom_scope(&scope),
"scope must be user--<id> or group--<id>"
); Type guard
// Rust narrowing predicate for WeCom WS scopes
fn wecom_scope_kind(scope: &str) -> Option<(&'static str, &str)> {
if let Some(id) = scope.strip_prefix("user--") {
return Some(("user", id));
}
if let Some(id) = scope.strip_prefix("group--") {
return Some(("group", id));
}
None
}
// usage: if let Some((kind, id)) = wecom_scope_kind(scope) { channel.send(...) } Try / catch
Validate scope shape before sending (predicate above); if a send still fails with 'invalid scope format', log the exact string and fix the producer that built it — do not retry, the format is deterministic.
Prevention
- Centralize scope construction in one helper that emits the double-dash format
- Prefer echoing reply scopes from inbound messages over hand-building them
- Unit-test the double-dash format wherever scopes are produced
When it happens
Trigger: Sending/replying with a scope built as a raw userid, 'user-<id>' (single dash), an email-style WeCom address, or a chatid missing the 'group--' prefix.
Common situations: reply_scope or target config populated with a copied bare WeCom userid; scope strings constructed with a different separator in user code; scopes sourced from external systems that use plain IDs.
Related errors
- WeCom webhook send failed ({status}): {err}
- WeCom WS {command} ack timeout after {}s (req_id={req_id})
- WeCom max_file_size_bytes is zero
- missing msgtype
- proxy.scope='services' requires a non-empty proxy.services l
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/232209ce83e05486.
Report an issue: GitHub.