{"record":{"id":"adf2c66ee19027f3","repo":"Hmbown/CodeWhale","slug":"invalid-bundle-url","errorCode":null,"errorMessage":"invalid bundle URL","messagePattern":"invalid bundle URL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cli/src/config_bundles.rs","lineNumber":754,"sourceCode":"            \"could not resolve bundle path component {}\",\n            deepest_existing.display()\n        )\n    })?;\n    if !resolved.starts_with(&canonical_base) {\n        bail!(\"bundle path {candidate:?} escapes the config directory via a symlink; refused\");\n    }\n    Ok(joined)\n}\n\n// ---------------------------------------------------------------------------\n// Remote fetch\n// ---------------------------------------------------------------------------\n\n/// Fetch a bundle over HTTPS (or plain http on loopback only) with a hard\n/// size cap, a timeout, and bounded redirects. Mirrors the skill installer's\n/// fetch bounds.\npub fn fetch_bundle(url: &str) -> Result<Vec<u8>> {\n    let mut current_url = reqwest::Url::parse(url).map_err(|_| anyhow!(\"invalid bundle URL\"))?;\n    validate_bundle_url(&current_url)?;\n    let initial_scheme = current_url.scheme().to_string();\n\n    let client = codewhale_release::platform_blocking_http_client_builder()\n        .timeout(std::time::Duration::from_secs(FETCH_TIMEOUT_SECS))\n        // Redirect targets must pass the same scheme/host policy as the\n        // initial request, so redirects are followed explicitly below.\n        .redirect(reqwest::redirect::Policy::none())\n        .build()\n        .map_err(|_| anyhow!(\"building bundle fetch client failed\"))?;\n    let mut redirects = 0usize;\n    let response = loop {\n        let response = client\n            .get(current_url.clone())\n            .send()\n            // reqwest errors can include the full URL (including its query or\n            // userinfo), so keep transport failures deliberately URL-free.\n            .map_err(|_| anyhow!(\"bundle fetch request failed\"))?;","sourceCodeStart":736,"sourceCodeEnd":772,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/config_bundles.rs#L736-L772","documentation":"`fetch_bundle` first parses the given bundle URL with `reqwest::Url::parse`; any parse failure is mapped to this error. It is thrown before any network activity, so it always means the URL string itself is not a valid absolute URL.","triggerScenarios":"`fetch_bundle` / `codewhale` bundle import invoked with a URL string that fails RFC 3986 parsing — missing scheme (`example.com/bundle.zip`), invalid characters, empty string, or a relative path.","commonSituations":"Forgetting the `https://` scheme; copying a URL with surrounding whitespace or shell-mangled characters; passing a local file path instead of a URL.","solutions":["Pass a full absolute URL including the scheme: `https://host/path` (plain `http://` is only accepted for loopback hosts).","Trim whitespace and shell-quoting artifacts from the URL.","For local bundles, use the file/import-from-path flow rather than a URL."],"exampleFix":"// before\ncodewhale config-bundles import example.com/bundle.zip\n// after\ncodewhale config-bundles import https://example.com/bundle.zip","handlingStrategy":"validation","validationCode":"fn is_fetchable_bundle_url(s: &str) -> bool {\n    match reqwest::Url::parse(s) {\n        Ok(u) => matches!(u.scheme(), \"https\") || (u.scheme() == \"http\" && u.host_str().map_or(false, |h| h == \"localhost\" || h == \"127.0.0.1\")),\n        Err(_) => false,\n    }\n}","typeGuard":"fn is_absolute_https(s: &str) -> bool {\n    reqwest::Url::parse(s).map(|u| u.scheme() == \"https\").unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Always include the scheme in bundle URLs.","Trim and shell-quote URLs passed on the command line.","Use file paths, not URLs, for local bundles."],"tags":["url","validation","cli"],"backgroundTag":"invalid-url","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}