{"id":"621363497c78db97","repo":"rust-lang/cargo","slug":"sparse-registry-url-must-end-in-a-slash-url","errorCode":null,"errorMessage":"sparse registry url must end in a slash `/`: {url}","messagePattern":"sparse registry url must end in a slash `/`: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/sources/registry/http_remote.rs","lineNumber":393,"sourceCode":"    login_url: RefCell<Option<Url>>,\n\n    /// Headers received with an HTTP 401.\n    auth_error_headers: RefCell<Vec<String>>,\n\n    /// Disables status messages.\n    quiet: Cell<bool>,\n}\n\nimpl<'gctx> HttpBackend<'gctx> {\n    pub fn new(\n        source_id: SourceId,\n        gctx: &'gctx GlobalContext,\n        name: &str,\n    ) -> CargoResult<HttpBackend<'gctx>> {\n        let url = source_id.url().as_str();\n        // Ensure the url ends with a slash so we can concatenate paths.\n        if !url.ends_with('/') {\n            anyhow::bail!(\"sparse registry url must end in a slash `/`: {url}\")\n        }\n        assert!(source_id.is_sparse());\n        let url = url\n            .strip_prefix(\"sparse+\")\n            .expect(\"sparse registry needs sparse+ prefix\")\n            .into_url()\n            .expect(\"a url with the sparse+ stripped should still be valid\");\n\n        let index_cache_path = gctx.registry_index_path().join(name);\n        Ok(HttpBackend {\n            index_cache_path: index_cache_path.clone(),\n            crate_cache_path: gctx.registry_cache_path().join(name),\n            source_id,\n            gctx,\n            url,\n            progress: RefCell::new(Some(Progress::with_style(\n                \"Fetch\",\n                ProgressStyle::Indeterminate,","sourceCodeStart":375,"sourceCodeEnd":411,"githubUrl":"https://github.com/rust-lang/cargo/blob/0e07a155371a6ce88ae53a2c00df940280c09a67/src/sources/registry/http_remote.rs#L375-L411","documentation":"When constructing an `HttpBackend` for a sparse registry (src/sources/registry/http_remote.rs:390), Cargo requires the index URL to end with `/` because it builds crate index URLs by string concatenation (`url + relative_path`). A URL without a trailing slash would silently produce wrong paths, so it bails hard.","triggerScenarios":"Configuring a registry with `index = \"sparse+https://example.com/index\"` (no trailing slash); passing a bare URL to `cargo --index sparse+https://...`. Triggered at `HttpBackend::new` during source creation.","commonSituations":"Copy-pasting a sparse URL without the trailing slash in `.cargo/config.toml`, `Cargo.toml` `[registries]`, or a `--index` CLI argument; migrating from the git protocol (which doesn't require a trailing slash) to sparse.","solutions":["Add a trailing `/` to the index URL everywhere it's configured.","Search all config sources (`.cargo/config.toml`, `Cargo.toml`, `~/.cargo/config.toml`, `CARGO_REGISTRIES_<NAME>_INDEX` env) for the offending URL."],"exampleFix":"# before\n[registries.my-reg]\nindex = \"sparse+https://index.crates.io\"   # -> sparse registry url must end in a slash\n\n# after\n[registries.my-reg]\nindex = \"sparse+https://index.crates.io/\"","handlingStrategy":"validation","validationCode":"// Validate the trailing slash when constructing a sparse registry SourceId.\nfn ensure_trailing_slash(url: &str) -> CargoResult<()> {\n    if !url.ends_with('/') {\n        anyhow::bail!(\"sparse registry url must end in a slash `/`: {url}\");\n    }\n    Ok(())\n}","typeGuard":"pub fn is_well_formed_sparse_url(url: &str) -> bool {\n    url.starts_with(\"sparse+\") && url.ends_with('/')\n}","tryCatchPattern":null,"preventionTips":["Always end sparse index URLs with `/`.","Centralize registry URL validation in config-loading code so misconfiguration fails early."],"tags":["cargo","registry","sparse","url","config"],"analyzedSha":"0e07a155371a6ce88ae53a2c00df940280c09a67","analyzedAt":"2026-08-06T01:46:58.334Z","schemaVersion":2}