zeroclaw-labs/zeroclaw · error · anyhow::Error

{field_name} must use https://, got {url}

Error message

{field_name} must use https://, got {url}

What it means

https_base_url validates the WeChat channel's api_base_url and cdn_base_url during WeChatChannel::new: after trimming whitespace and trailing '/', the URL must start with https:// or construction fails, naming the field and the offending value. Defaults (DEFAULT_API_BASE_URL / CDN_BASE_URL) are https, so this only fires on explicitly configured values. The rule exists because the channel sends/decrypts authenticated media and API traffic that must not traverse plaintext HTTP.

Source

Thrown at crates/zeroclaw-channels/src/wechat.rs:438

            anyhow::Error::msg("media nested hex aes_key must be 16 bytes")
        });
    }

    anyhow::bail!(
        "media aes_key must decode to 16 raw bytes or 32 hex chars, got {} bytes",
        decoded.len()
    )
}

fn https_base_url(
    field_name: &str,
    value: Option<String>,
    default: &str,
) -> anyhow::Result<String> {
    let url = value.unwrap_or_else(|| default.to_string());
    let url = url.trim().trim_end_matches('/').to_string();
    if !url.starts_with("https://") {
        anyhow::bail!("{field_name} must use https://, got {url}");
    }
    Ok(url)
}

/// Interpret an iLink `sendmessage` response body, returning a description
/// of the failure when the API reported one.
///
/// The iLink API reports send failures as HTTP 200 with a non-zero
/// `ret`/`errcode` in the JSON body — the same envelope the getUpdates
/// sync loop parses. Checking only the HTTP status treats those failures
/// (e.g. an expired or missing `context_token`) as success, so the message
/// is silently dropped.
///
/// An empty or non-JSON 2xx body carries no envelope to inspect and is
/// treated as success, preserving the pre-check behavior for those shapes.
fn sendmessage_body_error(body: &str) -> Option<String> {
    if body.trim().is_empty() {
        return None;

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Change the configured api_base_url / cdn_base_url to start with https:// (keep the rest of the path identical).
  2. If the origin is plaintext, put an HTTPS reverse proxy (nginx/Caddy with TLS) in front and point the channel at the https proxy URL.
  3. For local testing, use a locally trusted cert (mkcert) so the URL is still https://.
  4. Or simply omit both keys to fall back to the built-in https defaults.

Example fix

# before
[channels.wechat.bot]
api_base_url = "http://ilink.internal:8080"  # -> "api_base_url must use https://, got http://ilink.internal:8080"

# after — terminate TLS upstream and use https
[channels.wechat.bot]
api_base_url = "https://ilink.internal"
Defensive patterns

Strategy: validation

Validate before calling

// Reject non-https overrides before constructing the channel.
fn ensure_https(field: &str, url: Option<&str>) -> anyhow::Result<()> {
    if let Some(u) = url {
        let t = u.trim().trim_end_matches('/');
        anyhow::ensure!(t.starts_with("https://"), "{field} must use https://, got {t}");
    }
    Ok(())
}
ensure_https("api_base_url", cfg.api_base_url.as_deref())?;
ensure_https("cdn_base_url", cfg.cdn_base_url.as_deref())?;

Try / catch

match WeChatChannel::new(/* ... */).await {
    Ok(ch) => { /* use channel */ }
    Err(e) if e.to_string().contains("must use https://") => {
        // Config defect caught at construction: fix the URL or front the origin
        // with an HTTPS proxy, then restart. No retry path exists.
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: WeChatChannel::new with config [channels.wechat.<alias>] api_base_url or cdn_base_url set to http://ilink.example.com, http://localhost:8080, a ws:// value, or a URL with a typo like https:/ (single slash). Also fires when someone points the channel at a local mock/proxy over plain http for testing.

Common situations: Local development against a self-hosted iLink mock over http; a private deployment that terminated TLS on an upstream proxy but configured the internal http:// address; migrating a config from an environment where http was tolerated.

Related errors


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