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

  1. Format scopes as 'user--<userid>' for direct and 'group--<chatid>' for groups — double dash, no spaces
  2. Derive scope strings from inbound messages (echo the reply scope) instead of hand-building them
  3. 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

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


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