{"record":{"id":"36932165b25bfec2","repo":"Hmbown/CodeWhale","slug":"pass-a-host-like-github-com-not-a-url-path","errorCode":null,"errorMessage":"Pass a host like `github.com`, not a URL path","messagePattern":"Pass a host like `github\\.com`, not a URL path","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/tui/src/commands/groups/utility/network.rs","lineNumber":272,"sourceCode":"    if !list\n        .iter()\n        .any(|existing| normalize_host_for_compare(existing) == host)\n    {\n        list.push(host.to_string());\n    }\n}\n\nfn remove_host(list: &mut Vec<String>, host: &str) {\n    list.retain(|existing| normalize_host_for_compare(existing) != host);\n}\n\nfn normalize_host_arg(input: &str) -> anyhow::Result<String> {\n    let trimmed = input.trim();\n    let host = if trimmed.starts_with(\"http://\") || trimmed.starts_with(\"https://\") {\n        host_from_url(trimmed).context(\"URL must include a host\")?\n    } else {\n        if trimmed.contains(\"://\") || trimmed.contains('/') {\n            bail!(\"Pass a host like `github.com`, not a URL path\");\n        }\n        trimmed.to_string()\n    };\n\n    let normalized = normalize_host_for_compare(&host);\n    if normalized.is_empty() {\n        bail!(\"host cannot be empty\");\n    }\n    Ok(normalized)\n}\n\nfn normalize_host_for_compare(host: &str) -> String {\n    let trimmed = host.trim().trim_end_matches('.').to_ascii_lowercase();\n    if let Some(rest) = trimmed.strip_prefix(\"*.\") {\n        format!(\".{rest}\")\n    } else {\n        trimmed\n    }","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/commands/groups/utility/network.rs#L254-L290","documentation":"`normalize_host_arg` validates the host argument of the `/network allow|deny <host>` commands. Inputs starting with `http://` or `https://` are reduced to their host via `host_from_url`; any other input containing `://` (ftp://, ssh://, git://) or a `/` path component bails, because the network policy list stores bare hosts, not URLs.","triggerScenarios":"`/network deny github.com/org/repo` (URL path), `/network allow ftp://mirror.example.org`, `/network deny api.example.com/v1/entities`.","commonSituations":"Pasting a full git clone URL or API endpoint URL instead of the domain; using ssh:// or git:// scheme URLs, which the parser deliberately refuses (only http(s) is unwrapped).","solutions":["Pass the bare host: `/network deny github.com`.","If you paste an http(s) URL the host is extracted automatically, but a bare host is the canonical form.","To cover a domain and all its subdomains use wildcard syntax `*.example.com`.","Check the argument for `/` or a non-HTTP `://` scheme before submitting."],"exampleFix":"# before\n/network deny https://github.com/codewhale/codewhale\n# after\n/network deny github.com","handlingStrategy":"validation","validationCode":"fn normalize_host_arg(input: &str) -> Option<String> {\n    let t = input.trim();\n    let host = if t.starts_with(\"http://\") || t.starts_with(\"https://\") {\n        reqwest::Url::parse(t).ok()?.host_str()?.to_ascii_lowercase()\n    } else {\n        if t.contains(\"://\") || t.contains('/') {\n            return None;\n        }\n        t.to_string()\n    };\n    let host = host.trim_end_matches('.').to_ascii_lowercase();\n    (!host.is_empty()).then_some(host)\n}","typeGuard":"fn is_bare_host(input: &str) -> bool {\n    let t = input.trim();\n    !t.is_empty() && !t.contains('/') && !t.contains(\"://\")\n}","tryCatchPattern":null,"preventionTips":["Strip URLs down to the hostname in input fields before submission.","Accept and unwrap http(s) URLs at the caller so pasted URLs still work.","Never store URL paths or scheme prefixes in host allow/deny lists."],"tags":["network","hostname","url-parsing","tui"],"backgroundTag":"invalid-hostname","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}