{"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/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/task/workspace.rs#L1172-L1208","documentation":"normalize_project_root() converts the root of a [monorepo.projects.\"<id>\"] override (or a provider-supplied root) into a canonical workspace-relative path. The first rule is that the root must be relative: an absolute path (one where Path::is_absolute() is true, e.g. \"/home/me/repo/crates/a\") is rejected immediately because project roots are stored relative to the workspace root so the graph stays portable and cacheable.","triggerScenarios":"Setting root = \"/absolute/path/to/project\" or \"C:\\\\repo\\\\crates\\\\a\" in [monorepo.projects.\"<id>\"]; generating the override from tooling that emits absolute paths (editor plugins, generators using std::fs::canonicalize output); symlinks resolved to absolute paths before insertion.","commonSituations":"Script-generated mise.toml entries; developers assuming root behaves like a filesystem path; porting config between machines where absolute paths silently differ; Windows users pasting backslash absolute paths.","solutions":["Express the root relative to the workspace root: root = \"crates/a\"","If tooling generates the entry, strip the workspace-root prefix (path.strip_prefix(workspace_root)) before writing","Use forward slashes and no leading './' or '/'"],"exampleFix":"# before (mise.toml)\n[monorepo.projects.\"cargo:app\"]\nroot = \"/home/me/monorepo/crates/app\"\n\n# after\n[monorepo.projects.\"cargo:app\"]\nroot = \"crates/app\"","handlingStrategy":"validation","validationCode":"let root = std::path::Path::new(&cfg.root.unwrap_or_default());\nif root.is_absolute() || root.components().any(|c| matches!(c, std::path::Component::Prefix(_) | std::path::Component::RootDir)) {\n    return Err(eyre::eyre!(\"project root must be workspace-relative: {}\", root.display()));\n}","typeGuard":"fn root_is_relative(root: &Path) -> bool {\n    !root.is_absolute() && !root.components().any(|c| matches!(c, std::path::Component::Prefix(_) | std::path::Component::RootDir))\n}","tryCatchPattern":null,"preventionTips":["Store and emit project roots relative to the workspace root in all tooling","When generating config from absolute paths, always apply strip_prefix(workspace_root) first","Use forward slashes in TOML paths; they work on Windows too and avoid Prefix components"],"tags":["rust","mise","workspace","path-validation","config"],"backgroundTag":"absolute-path-rejected","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}