rust-lang/cargo · error

sparse registry url must end in a slash `/

Error message

sparse registry url must end in a slash `/`: {url}

What it means

When constructing an `HttpBackend` for a sparse registry, Cargo requires the index URL to end with `/` because it concatenates relative paths onto it. A URL without a trailing slash would produce wrong request URLs (e.g. `sparse+https://h/indexcr/te` instead of `sparse+https://h/index/cr/te`), so Cargo bails at construction.

Solutions

  1. Add a trailing `/` to the `index` URL in `.cargo/config.toml` or `Cargo.toml` `[source]` override.
  2. Validate registry URLs in a setup script / pre-commit lint.
  3. Double-check environment variables like `CARGO_REGISTRIES_<NAME>_INDEX` for trailing slash.

Example fix

# before
[registries.myreg]
index = "sparse+https://myreg.example.com/index"

# after
[registries.myreg]
index = "sparse+https://myreg.example.com/index/"
Defensive patterns

Strategy: validation

Validate before calling

fn require_trailing_slash(url: &str) -> Result<(), anyhow::Error> {
    if !url.ends_with('/') { anyhow::bail!("sparse registry url must end in '/': {url}"); }
    Ok(())
}

Type guard

fn url_has_trailing_slash(u: &str) -> bool { u.ends_with('/') }

Try / catch

let url = if source_id.url().as_str().ends_with('/') {
    source_id.url().as_str().to_string()
} else {
    format!("{}/", source_id.url().as_str()) // self-heal instead of erroring
};

Prevention

When it happens

Trigger: `HttpBackend::new` is called with a `source_id` whose URL does not end with `/`. Happens when a user configures `registries.<name>.index = "sparse+https://host/path"` without the trailing slash, or passes such a URL programmatically.

Common situations: User-typed sparse registry URL missing trailing slash; CI config templating that strips trailing slashes; migration from git index to sparse index where the slash was omitted.

Related errors


AI-assisted analysis of rust-lang/cargo@eb98b54bc9 (2026-08-11). Data as JSON: /api/errors/621363497c78db97. Report an issue: GitHub.

Appendix: source

Thrown at src/sources/registry/http_remote.rs:393

    login_url: RefCell<Option<Url>>,

    /// Headers received with an HTTP 401.
    auth_error_headers: RefCell<Vec<String>>,

    /// Disables status messages.
    quiet: Cell<bool>,
}

impl<'gctx> HttpBackend<'gctx> {
    pub fn new(
        source_id: SourceId,
        gctx: &'gctx GlobalContext,
        name: &str,
    ) -> CargoResult<HttpBackend<'gctx>> {
        let url = source_id.url().as_str();
        // Ensure the url ends with a slash so we can concatenate paths.
        if !url.ends_with('/') {
            anyhow::bail!("sparse registry url must end in a slash `/`: {url}")
        }
        assert!(source_id.is_sparse());
        let url = url
            .strip_prefix("sparse+")
            .expect("sparse registry needs sparse+ prefix")
            .into_url()
            .expect("a url with the sparse+ stripped should still be valid");

        let index_cache_path = gctx.registry_index_path().join(name);
        Ok(HttpBackend {
            index_cache_path: index_cache_path.clone(),
            crate_cache_path: gctx.registry_cache_path().join(name),
            source_id,
            gctx,
            url,
            progress: RefCell::new(Some(Progress::with_style(
                "Fetch",
                ProgressStyle::Indeterminate,

View on GitHub (pinned to eb98b54bc9)