{"record":{"id":"ef47f0c49201e920","repo":"zeroclaw-labs/zeroclaw","slug":"field-name-must-use-https-got-url","errorCode":null,"errorMessage":"{field_name} must use https://, got {url}","messagePattern":"(.+?) must use https://, got (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/wechat.rs","lineNumber":438,"sourceCode":"            anyhow::Error::msg(\"media nested hex aes_key must be 16 bytes\")\n        });\n    }\n\n    anyhow::bail!(\n        \"media aes_key must decode to 16 raw bytes or 32 hex chars, got {} bytes\",\n        decoded.len()\n    )\n}\n\nfn https_base_url(\n    field_name: &str,\n    value: Option<String>,\n    default: &str,\n) -> anyhow::Result<String> {\n    let url = value.unwrap_or_else(|| default.to_string());\n    let url = url.trim().trim_end_matches('/').to_string();\n    if !url.starts_with(\"https://\") {\n        anyhow::bail!(\"{field_name} must use https://, got {url}\");\n    }\n    Ok(url)\n}\n\n/// Interpret an iLink `sendmessage` response body, returning a description\n/// of the failure when the API reported one.\n///\n/// The iLink API reports send failures as HTTP 200 with a non-zero\n/// `ret`/`errcode` in the JSON body — the same envelope the getUpdates\n/// sync loop parses. Checking only the HTTP status treats those failures\n/// (e.g. an expired or missing `context_token`) as success, so the message\n/// is silently dropped.\n///\n/// An empty or non-JSON 2xx body carries no envelope to inspect and is\n/// treated as success, preserving the pre-check behavior for those shapes.\nfn sendmessage_body_error(body: &str) -> Option<String> {\n    if body.trim().is_empty() {\n        return None;","sourceCodeStart":420,"sourceCodeEnd":456,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/wechat.rs#L420-L456","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Change the configured api_base_url / cdn_base_url to start with https:// (keep the rest of the path identical).","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.","For local testing, use a locally trusted cert (mkcert) so the URL is still https://.","Or simply omit both keys to fall back to the built-in https defaults."],"exampleFix":"# before\n[channels.wechat.bot]\napi_base_url = \"http://ilink.internal:8080\"  # -> \"api_base_url must use https://, got http://ilink.internal:8080\"\n\n# after — terminate TLS upstream and use https\n[channels.wechat.bot]\napi_base_url = \"https://ilink.internal\"","handlingStrategy":"validation","validationCode":"// Reject non-https overrides before constructing the channel.\nfn ensure_https(field: &str, url: Option<&str>) -> anyhow::Result<()> {\n    if let Some(u) = url {\n        let t = u.trim().trim_end_matches('/');\n        anyhow::ensure!(t.starts_with(\"https://\"), \"{field} must use https://, got {t}\");\n    }\n    Ok(())\n}\nensure_https(\"api_base_url\", cfg.api_base_url.as_deref())?;\nensure_https(\"cdn_base_url\", cfg.cdn_base_url.as_deref())?;","typeGuard":null,"tryCatchPattern":"match WeChatChannel::new(/* ... */).await {\n    Ok(ch) => { /* use channel */ }\n    Err(e) if e.to_string().contains(\"must use https://\") => {\n        // Config defect caught at construction: fix the URL or front the origin\n        // with an HTTPS proxy, then restart. No retry path exists.\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Lint config in CI: every *_base_url under [channels.wechat.*] must start with https://.","For local mocks, use mkcert/TLS proxies so test configs keep the https scheme.","Prefer omitting api_base_url/cdn_base_url unless you truly run a private endpoint — defaults are https.","Watch for the single-slash typo 'https:/' — it fails the check too."],"tags":["wechat","config","https","url-validation","tls"],"backgroundTag":"https-url-required","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}