Hmbown/CodeWhale · error

invalid bundle URL

Error message

invalid bundle URL

What it means

`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.

Solutions

  1. Pass a full absolute URL including the scheme: `https://host/path` (plain `http://` is only accepted for loopback hosts).
  2. Trim whitespace and shell-quoting artifacts from the URL.
  3. For local bundles, use the file/import-from-path flow rather than a URL.

Example fix

// before
codewhale config-bundles import example.com/bundle.zip
// after
codewhale config-bundles import https://example.com/bundle.zip
Defensive patterns

Strategy: validation

Validate before calling

fn is_fetchable_bundle_url(s: &str) -> bool {
    match reqwest::Url::parse(s) {
        Ok(u) => matches!(u.scheme(), "https") || (u.scheme() == "http" && u.host_str().map_or(false, |h| h == "localhost" || h == "127.0.0.1")),
        Err(_) => false,
    }
}

Type guard

fn is_absolute_https(s: &str) -> bool {
    reqwest::Url::parse(s).map(|u| u.scheme() == "https").unwrap_or(false)
}

Prevention

When it happens

Trigger: `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.

Common situations: Forgetting the `https://` scheme; copying a URL with surrounding whitespace or shell-mangled characters; passing a local file path instead of a URL.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/adf2c66ee19027f3. Report an issue: GitHub.

Appendix: source

Thrown at crates/cli/src/config_bundles.rs:754

            "could not resolve bundle path component {}",
            deepest_existing.display()
        )
    })?;
    if !resolved.starts_with(&canonical_base) {
        bail!("bundle path {candidate:?} escapes the config directory via a symlink; refused");
    }
    Ok(joined)
}

// ---------------------------------------------------------------------------
// Remote fetch
// ---------------------------------------------------------------------------

/// Fetch a bundle over HTTPS (or plain http on loopback only) with a hard
/// size cap, a timeout, and bounded redirects. Mirrors the skill installer's
/// fetch bounds.
pub fn fetch_bundle(url: &str) -> Result<Vec<u8>> {
    let mut current_url = reqwest::Url::parse(url).map_err(|_| anyhow!("invalid bundle URL"))?;
    validate_bundle_url(&current_url)?;
    let initial_scheme = current_url.scheme().to_string();

    let client = codewhale_release::platform_blocking_http_client_builder()
        .timeout(std::time::Duration::from_secs(FETCH_TIMEOUT_SECS))
        // Redirect targets must pass the same scheme/host policy as the
        // initial request, so redirects are followed explicitly below.
        .redirect(reqwest::redirect::Policy::none())
        .build()
        .map_err(|_| anyhow!("building bundle fetch client failed"))?;
    let mut redirects = 0usize;
    let response = loop {
        let response = client
            .get(current_url.clone())
            .send()
            // reqwest errors can include the full URL (including its query or
            // userinfo), so keep transport failures deliberately URL-free.
            .map_err(|_| anyhow!("bundle fetch request failed"))?;

View on GitHub (pinned to 73e0f67d83)