{"record":{"id":"fbf1acb68a51aa9d","repo":"jdx/mise","slug":"workspace-project-id-has-absolute-root-root","errorCode":null,"errorMessage":"workspace project {id:?} has absolute root {root:?}; roots must be workspace-relative","messagePattern":"workspace project (.+?) has absolute root (.+?); roots must be workspace-relative","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/workspace.rs","lineNumber":1190,"sourceCode":"    project_id: &ProjectId,\n    field: &str,\n    dependencies: &BTreeSet<String>,\n) -> Result<BTreeSet<ProjectId>> {\n    dependencies\n        .iter()\n        .map(|dependency| {\n            dependency.parse::<ProjectId>().map_err(|err| {\n                eyre::eyre!(\n                    \"workspace project {project_id:?} has invalid {field} entry {dependency:?}: {err}\"\n                )\n            })\n        })\n        .collect()\n}\n\nfn normalize_project_root(id: &ProjectId, root: &Path) -> Result<PathBuf> {\n    if root.is_absolute() {\n        bail!(\n            \"workspace project {id:?} has absolute root {root:?}; roots must be workspace-relative\"\n        );\n    }\n\n    let mut normalized = PathBuf::new();\n    for component in root.components() {\n        match component {\n            Component::CurDir => {}\n            Component::Normal(component) => normalized.push(component),\n            Component::ParentDir => {\n                if !normalized.pop() {\n                    bail!(\n                        \"workspace project {id:?} has root {root:?} that escapes the workspace root\"\n                    );\n                }\n            }\n            Component::RootDir | Component::Prefix(_) => {\n                bail!(","sourceCodeStart":1172,"sourceCodeEnd":1208,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/task/workspace.rs#L1172-L1208","documentation":"normalize_project_root validates each workspace project's root path. Roots must be relative to the workspace root; an absolute path would let a project live outside the workspace and breaks workspace-relative path resolution, so mise throws this error.","triggerScenarios":"A [workspace.project] root value like \"/home/me/proj\" or \"C:\\\\proj\" is passed to normalize_project_root for project {id}.","commonSituations":"Copy-pasting an absolute path from a file manager into a project's root field; generating configs from scripts that emit absolute paths.","solutions":["Replace the absolute root with a path relative to the workspace root, e.g. \"services/api\"","Move the project directory under the workspace root if it currently lives outside it","Re-run after normalizing separators (use forward slashes / relative components)"],"exampleFix":"# before\n[workspace.project.api]\nroot = \"/home/me/proj/services/api\"\n# after\n[workspace.project.api]\nroot = \"services/api\"","handlingStrategy":"validation","validationCode":"fn ensure_relative_root(id: &str, root: &std::path::Path) -> Result<(), String> {\n    if root.is_absolute() {\n        return Err(format!(\"project {id}: root {root:?} must be workspace-relative\"));\n    }\n    Ok(())\n}","typeGuard":"let is_relative_root = |p: &std::path::Path| !p.is_absolute();","tryCatchPattern":"if let Err(e) = normalize_project_root(&id, &root) {\n    if e.to_string().contains(\"absolute root\") {\n        let rel = root.strip_prefix(base).unwrap_or(&root);\n        eprintln!(\"use workspace-relative root, e.g. {rel:?}\");\n    }\n}","preventionTips":["Always author root fields as paths relative to the workspace root","Have generators emit relative paths (strip the workspace base before writing)","Never paste absolute paths from editors or file managers into workspace configs"],"tags":["workspace","config","path"],"backgroundTag":"invalid-config-value","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}