jdx/mise · error
workspace project {id:?} has root {root:?} that escapes the
Error message
workspace project {id:?} has root {root:?} that escapes the workspace root What it means
normalize_project_root() walks the components of a relative project root; each '..' must be absorbed by a preceding normal component. If a '..' pops past the accumulated path (normalized.pop() returns false), the root escapes the workspace root and is rejected — e.g. root = "../sibling" or "crates/../../outside".
Source
Thrown at src/task/workspace.rs:1202
})
.collect()
}
fn normalize_project_root(id: &ProjectId, root: &Path) -> Result<PathBuf> {
if root.is_absolute() {
bail!(
"workspace project {id:?} has absolute root {root:?}; roots must be workspace-relative"
);
}
let mut normalized = PathBuf::new();
for component in root.components() {
match component {
Component::CurDir => {}
Component::Normal(component) => normalized.push(component),
Component::ParentDir => {
if !normalized.pop() {
bail!(
"workspace project {id:?} has root {root:?} that escapes the workspace root"
);
}
}
Component::RootDir | Component::Prefix(_) => {
bail!(
"workspace project {id:?} has absolute root {root:?}; roots must be workspace-relative"
);
}
}
}
if normalized.as_os_str().is_empty() {
normalized.push(".");
}
Ok(normalized)
}
fn normalize_workspace_path(path: &Path) -> Result<PathBuf> {View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Point the root at a directory inside the workspace root (no escaping '..' chain)
- If the project genuinely lives outside, move it inside or move the workspace root up so the target is contained
- Replace '../..' chains with the direct in-root relative path
Example fix
# before (mise.toml) [monorepo.projects."cargo:shared"] root = "../shared-monorepo/crates/shared" # after — vendored inside the workspace [monorepo.projects."cargo:shared"] root = "vendor/shared"
Defensive patterns
Strategy: validation
Validate before calling
fn stays_in_root(rel: &Path) -> bool {
let mut depth = 0usize;
for c in rel.components() {
match c {
std::path::Component::Normal(_) => depth += 1,
std::path::Component::ParentDir => { if depth == 0 { return false; } depth -= 1; }
_ => {}
}
}
true
}
assert!(stays_in_root(Path::new(&cfg.root.unwrap_or_default()))); Type guard
fn root_stays_in_workspace(root: &Path) -> bool {
let mut depth = 0;
root.components().all(|c| match c {
std::path::Component::Normal(_) => { depth += 1; true }
std::path::Component::ParentDir => { let ok = depth > 0; depth = depth.saturating_sub(1); ok }
_ => true,
})
} Prevention
- Keep every [monorepo.projects] root inside the monorepo checkout
- When the repo layout changes, grep overrides for '../' and re-baseline them
- If a project must live outside, relocate the workspace root rather than escaping with '..'
When it happens
Trigger: A [monorepo.projects] override root containing '..' segments that resolve above the workspace root; a provider trying to attach a project living outside the monorepo directory; workspace root moved deeper (directory restructure) so previously valid '../x' paths now escape.
Common situations: Monorepo root relocated one level (repo/repo instead of repo) breaking existing overrides; attempting to include a sibling checkout as a workspace project; copy-pasted relative paths from a different layout.
Related errors
- workspace path {path:?} escapes the workspace root
- workspace project {id:?} has absolute root {root:?}; roots m
- workspace path {path:?} is absolute; paths must be workspace
- Cargo workspace member pattern {member:?} must be relative
- mise upgrade --monorepo is not implemented yet
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/9470530e65ef8255.
Report an issue: GitHub.