{"record":{"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":null,"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/eb98b54bc9f3c74519f43d066cb3fd02ebc88df0/src/sources/registry/http_remote.rs#L375-L411","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","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."],"exampleFix":"# before\n[registries.myreg]\nindex = \"sparse+https://myreg.example.com/index\"\n\n# after\n[registries.myreg]\nindex = \"sparse+https://myreg.example.com/index/\"","handlingStrategy":"validation","validationCode":"fn require_trailing_slash(url: &str) -> Result<(), anyhow::Error> {\n    if !url.ends_with('/') { anyhow::bail!(\"sparse registry url must end in '/': {url}\"); }\n    Ok(())\n}","typeGuard":"fn url_has_trailing_slash(u: &str) -> bool { u.ends_with('/') }","tryCatchPattern":"let url = if source_id.url().as_str().ends_with('/') {\n    source_id.url().as_str().to_string()\n} else {\n    format!(\"{}/\", source_id.url().as_str()) // self-heal instead of erroring\n};","preventionTips":["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."],"tags":["cargo","registry","sparse","url","configuration","validation"],"backgroundTag":null,"analyzedSha":"eb98b54bc9f3c74519f43d066cb3fd02ebc88df0","analyzedAt":"2026-08-11T17:42:36.556Z","contentChangedAt":"2026-08-11T17:42:36.556Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}