{"record":{"id":"d4d218878069d2e1","repo":"jdx/mise","slug":"invalid-cargo-crate-name-name","errorCode":null,"errorMessage":"invalid Cargo crate name: {name:?}","messagePattern":"invalid Cargo crate name: (.+?)","errorType":"validation","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/backend/cargo.rs","lineNumber":522,"sourceCode":"        } else if let Some((user, repo)) = self.tool_name().split_once('/') {\n            format!(\"https://github.com/{user}/{repo}.git\").parse().ok()\n        } else {\n            None\n        }\n    }\n}\n\nfn format_tool_options(options: &[&'static str]) -> String {\n    options\n        .iter()\n        .map(|option| format!(\"`{option}`\"))\n        .collect::<Vec<_>>()\n        .join(\", \")\n}\n\nfn get_crate_url(name: &str) -> eyre::Result<Url> {\n    if name.is_empty() || !name.is_ascii() {\n        bail!(\"invalid Cargo crate name: {name:?}\");\n    }\n    let name = name.to_lowercase();\n    let url = match name.len() {\n        1 => format!(\"https://index.crates.io/1/{name}\"),\n        2 => format!(\"https://index.crates.io/2/{name}\"),\n        3 => format!(\"https://index.crates.io/3/{}/{name}\", &name[..1]),\n        _ => format!(\n            \"https://index.crates.io/{}/{}/{name}\",\n            &name[..2],\n            &name[2..4]\n        ),\n    };\n    Ok(url.parse()?)\n}\n\nfn parse_crate_versions(response: &str) -> eyre::Result<Vec<VersionInfo>> {\n    let mut versions = vec![];\n    for version in Deserializer::from_str(response).into_iter::<CrateVersion>() {","sourceCodeStart":504,"sourceCodeEnd":540,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/backend/cargo.rs#L504-L540","documentation":"get_crate_url() builds the crates.io sparse-index URL for a crate name so mise can list its versions. A crates.io index path can only be constructed from a non-empty, pure-ASCII name, so anything else (empty string or any non-ASCII character) is rejected with this bail before a URL is formed.","triggerScenarios":"`mise use cargo:<name>` / a mise.toml entry `\"cargo:<name>\"` where <name> is empty or contains non-ASCII characters (e.g. a typo, a unicode look-alike character, or programmatic construction of a ToolRequest with a bad name). Fails during version listing or install when get_crate_url is called.","commonSituations":"Copy-pasting a crate name with a homoglyph or trailing unicode whitespace; an empty name from a templating/scripting bug in generated config; refactoring tool ids that accidentally drops the crate name after `cargo:`.","solutions":["Fix the crate name in mise.toml / CLI argument to the exact ASCII spelling from crates.io","Check for invisible unicode characters (homoglyphs, NBSP) with a hex dump of the config line","If generating config programmatically, validate the name is non-empty ASCII before writing it"],"exampleFix":"# before\nmise use \"cargo:cargô\"\n# after\nmise use cargo:cargo","handlingStrategy":"validation","validationCode":"# shell: reject empty/non-ASCII crate names before calling mise\nname='bat'\n[[ \"$name\" =~ ^[A-Za-z0-9_-]+$ ]] && [[ -n \"$name\" ]] || { echo 'bad crate name'; exit 1; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Copy crate names verbatim from crates.io rather than typing them","Lint generated mise.toml for empty or non-ASCII tool identifiers","Sanitize names in scripts that build `cargo:<name>` strings"],"tags":["cargo","crate-name","input-validation","crates-io"],"backgroundTag":"invalid-identifier","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}