jdx/mise · error

workspace path {path:?} escapes the workspace root

Error message

workspace path {path:?} escapes the workspace root

What it means

While normalizing a workspace-relative path, normalize_workspace_path() rejects any '..' that pops past the path start: the path escapes the workspace root. This guards affected_projects_for_paths()/map_paths_to_projects() against out-of-tree paths like "../other-repo/file" that could otherwise never match a project root.

Source

Thrown at src/task/workspace.rs:1232

    if normalized.as_os_str().is_empty() {
        normalized.push(".");
    }
    Ok(normalized)
}

fn normalize_workspace_path(path: &Path) -> Result<PathBuf> {
    if path.is_absolute() {
        bail!("workspace path {path:?} is absolute; paths must be workspace-relative");
    }

    let mut normalized = PathBuf::new();
    for component in path.components() {
        match component {
            Component::CurDir => {}
            Component::Normal(component) => normalized.push(component),
            Component::ParentDir => {
                if !normalized.pop() {
                    bail!("workspace path {path:?} escapes the workspace root");
                }
            }
            Component::RootDir | Component::Prefix(_) => {
                bail!("workspace path {path:?} is absolute; paths must be workspace-relative");
            }
        }
    }
    if normalized.as_os_str().is_empty() {
        normalized.push(".");
    }
    Ok(normalized)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Debug)]

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Strip the workspace-root prefix from absolute paths instead of building '../' relative paths
  2. Run git with -C <workspace-root> --relative --name-only so diffs are root-relative
  3. Filter out paths that strip_prefix(root) cannot resolve — they are outside the workspace

Example fix

# before
 mise$ git diff --name-only   # emits ../../crates/app/src/lib.rs

# after
 git -C /repo diff --relative --name-only   # emits crates/app/src/lib.rs
Defensive patterns

Strategy: validation

Validate before calling

fn within_root(root: &Path, rel: &Path) -> bool {
    let mut depth = 0;
    rel.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,
    }) && root.join(rel).starts_with(root)
}
let safe: Vec<_> = paths.into_iter().filter(|p| within_root(root, p)).collect();

Type guard

fn path_does_not_escape(root: &Path, p: &Path) -> bool {
    !p.is_absolute() && root.join(p).canonicalize().map(|c| c.starts_with(root)).unwrap_or(true)
}

Prevention

When it happens

Trigger: Passing changed-path lists containing '../' entries: output of git diff run in a subdirectory without --relative, file-watcher events outside the root, or manually constructed diffs combining directory changes with '..'.

Common situations: Running git diff inside a project subdirectory (paths come out as ../../style); watching a parent directory that contains the workspace; joining relative paths incorrectly before calling the API.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/1f9a74dd950e76a1. Report an issue: GitHub.