{"record":{"id":"055d5a846fccb300","repo":"Hmbown/CodeWhale","slug":"invalid-registry-url-registry-url","errorCode":null,"errorMessage":"invalid registry url: {registry_url}","messagePattern":"invalid registry url: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/skills/install.rs","lineNumber":543,"sourceCode":"    if !target.join(INSTALLED_FROM_MARKER).exists() {\n        return Err(InstallError::NotInstalledHere(name.to_string()).into());\n    }\n    let content_digest = super::package_digest::compute_package_digest(&target)\n        .with_context(|| format!(\"cannot compute content digest for {}\", target.display()))?;\n    write_trust_v2(&target, &content_digest)?;\n    Ok(())\n}\n\n/// Fetch the curated registry and return the parsed entries.\n///\n/// Honours `network` (skipping the call entirely on Deny / Prompt).\npub async fn fetch_registry(\n    network: &NetworkPolicy,\n    registry_url: &str,\n) -> Result<RegistryFetchResult> {\n    let host = match host_from_url(registry_url) {\n        Some(host) => host,\n        None => bail!(\"invalid registry url: {registry_url}\"),\n    };\n    match network.decide(&host) {\n        Decision::Allow => {}\n        Decision::Deny => return Ok(RegistryFetchResult::Denied(host)),\n        Decision::Prompt => return Ok(RegistryFetchResult::NeedsApproval(host)),\n    }\n    let body = reqwest_client()\n        .get(registry_url)\n        .send()\n        .await\n        .with_context(|| format!(\"failed to fetch registry {registry_url}\"))?\n        .error_for_status()\n        .with_context(|| format!(\"registry {registry_url} returned an error status\"))?\n        .text()\n        .await\n        .with_context(|| format!(\"failed to read registry body from {registry_url}\"))?;\n    let parsed: RegistryDocument = serde_json::from_str(&body)\n        .with_context(|| format!(\"failed to parse registry json from {registry_url}\"))?;","sourceCodeStart":525,"sourceCodeEnd":561,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/skills/install.rs#L525-L561","documentation":"fetch_registry extracts a host from the configured registry URL before consulting the network policy; when host_from_url returns None (scheme missing or URL unparseable) it bails immediately. This keeps the policy engine from ever matching against a hostless URL, and it fails before any request is made.","triggerScenarios":"A registry_url configuration like 'skills.example.com/registry.json' (no scheme), 'ftp://...' (unsupported scheme), or a malformed string with spaces. Any install or registry-list flow that reaches fetch_registry with that configuration fails here.","commonSituations":"Hand-edited config dropping the 'https://', environment variables carrying a bare host, and copy-paste truncation of the URL.","solutions":["Set the registry URL with an explicit http(s) scheme and no stray characters.","Validate with a URL parser (url::Url::parse) at config load time, not at install time.","If the registry moved, update the setting; do not rely on redirects at this layer."],"exampleFix":"# before\nregistry = \"skills.example.com/registry.json\"\n\n# after\nregistry = \"https://skills.example.com/registry.json\"","handlingStrategy":"validation","validationCode":"fn registry_url_ok(url: &str) -> bool {\n    url::Url::parse(url)\n        .ok()\n        .and_then(|u| u.host_str().map(|h| !h.is_empty()))\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate the registry URL at config load time with url::Url::parse.","Always include the http(s) scheme; a bare host will not parse to a host-based URL here.","Add a config lint that rejects scheme-less URLs before first use."],"tags":["skills","registry","url","validation","rust"],"backgroundTag":"invalid-registry-url","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}