{"record":{"id":"59b15a7461a54cc2","repo":"jdx/mise","slug":"cargo-workspace-member-pattern-member-must-be","errorCode":null,"errorMessage":"Cargo workspace member pattern {member:?} must be relative","messagePattern":"Cargo workspace member pattern (.+?) must be relative","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/workspace/cargo.rs","lineNumber":254,"sourceCode":"fn discover_members(\n    pattern_root: &Path,\n    canonical_root: &Path,\n    members: &[String],\n    excludes: &[Pattern],\n    context: &WorkspaceDiscoveryContext,\n) -> Result<BTreeSet<PathBuf>> {\n    let options = MatchOptions {\n        case_sensitive: true,\n        require_literal_separator: true,\n        require_literal_leading_dot: true,\n    };\n    // Use the caller's non-canonical path for globbing. Windows canonical paths\n    // use a verbatim `\\\\?\\` prefix whose `?` is interpreted as a glob token.\n    let escaped_root = Pattern::escape(&pattern_root.to_string_lossy());\n    let mut roots = BTreeSet::new();\n    for member in members {\n        if Path::new(member).is_absolute() {\n            bail!(\"Cargo workspace member pattern {member:?} must be relative\");\n        }\n        let pattern = format!(\"{escaped_root}/{member}\");\n        for candidate in glob::glob_with(&pattern, options)\n            .wrap_err_with(|| format!(\"invalid Cargo workspace member pattern {member:?}\"))?\n        {\n            let candidate = candidate.wrap_err_with(|| {\n                format!(\"failed to evaluate Cargo workspace member pattern {member:?}\")\n            })?;\n            if !context.is_file(&candidate.join(CARGO_TOML)) {\n                continue;\n            }\n            let candidate = context.canonicalize(&candidate).wrap_err_with(|| {\n                format!(\n                    \"failed to resolve Cargo workspace member {}\",\n                    candidate.display()\n                )\n            })?;\n            let relative = relative_root(canonical_root, &candidate)?;","sourceCodeStart":236,"sourceCodeEnd":272,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/task/workspace/cargo.rs#L236-L272","documentation":"The Cargo workspace provider expands each entry of [workspace] members (and default-members) in the root Cargo.toml as a glob under the workspace root. Entries must be relative patterns: an absolute member path is rejected before globbing because mise composes the pattern as \"{root}/{member}\" and an absolute member would both break that composition and break portability of the manifest.","triggerScenarios":"A root Cargo.toml containing members = [\"/Users/me/monorepo/crates/*\"] or \"D:\\\\repo\\\\crates\\\\*\"; manifests edited by tools that write resolved absolute paths; Windows paths with a drive prefix (also absolute via the leading-root check).","commonSituations":"Generated Cargo.toml from a scaffolder; a teammate pasting a filesystem path into members; repos where cargo itself also rejects the absolute member (mise fails earlier with its own message).","solutions":["Change the member entry to a root-relative pattern such as \"crates/*\"","If the crate lives outside the workspace root, move it inside or relocate the workspace root — absolute members are not supported","After editing, verify with cargo metadata --no-deps that cargo itself accepts the manifest"],"exampleFix":"# before (root Cargo.toml)\n[workspace]\nmembers = [\"/home/me/monorepo/crates/*\"]\n\n# after\n[workspace]\nmembers = [\"crates/*\"]","handlingStrategy":"validation","validationCode":"let manifest: toml::Value = toml::from_str(&std::fs::read_to_string(root.join(\"Cargo.toml\"))?)?;\nfor member in manifest.get(\"workspace\").and_then(|w| w.get(\"members\")).and_then(|m| m.as_array()).into_iter().flatten().filter_map(|m| m.as_str()) {\n    if Path::new(member).is_absolute() {\n        return Err(eyre::eyre!(\"absolute workspace member {member}; make it root-relative\"));\n    }\n}","typeGuard":"fn cargo_members_are_relative(root: &Path) -> bool {\n    let Ok(text) = std::fs::read_to_string(root.join(\"Cargo.toml\")) else { return true };\n    toml::from_str::<toml::Value>(&text)\n        .ok()\n        .and_then(|v| v.get(\"workspace\").and_then(|w| w.get(\"members\")).and_then(|m| m.as_array()).cloned())\n        .map(|ms| ms.iter().filter_map(|m| m.as_str()).all(|m| !Path::new(m).is_absolute()))\n        .unwrap_or(true)\n}","tryCatchPattern":"if let Err(err) = CargoWorkspaceProvider.discover_with_context(root, &ctx) {\n    if err.to_string().contains(\"must be relative\") {\n        return Err(fix_hint(err, \"edit [workspace] members in Cargo.toml to use root-relative globs\"));\n    }\n    return Err(err);\n}","preventionTips":["Always write [workspace] members as root-relative globs (\"crates/*\")","Run cargo metadata --no-deps after manifest edits — cargo rejects absolute members too","Lint manifests in CI (taplo/cargo-validate) to catch machine-generated absolute paths"],"tags":["rust","mise","cargo","cargo-toml","workspace","path-validation"],"backgroundTag":"absolute-path-rejected","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}