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
- Add a trailing `/` to the `index` URL in `.cargo/config.toml` or `Cargo.toml` `[source]` override.
- Validate registry URLs in a setup script / pre-commit lint.
- 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
- Always configure sparse registry `index` with a trailing `/`.
- Lint `.cargo/config.toml` for sparse+ URLs missing the slash.
- Document the slash requirement for private registry users.
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
- config.json not found
- non UTF8 path
- invalid url ` `: cannot-be-a-base-URLs are not supported
- local registry index path is not a directory
- local registry path is not a directory
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)