jdx/mise · error

relative repo path ` ` must stay within the project root…

Error message

relative repo path `{path_raw}` must stay within the project root (no `..` or absolute segments)

What it means

RepoRequest::from_toml validates `[bootstrap.repos]` entries. When a repo key is a relative path, the expanded path must resolve inside the project root: any `..`, root, or platform-prefix component is rejected before the path is joined to the root. This blocks entries from pointing outside the project via traversal.

Solutions

  1. Change the key to an absolute path or a `~/`-relative path (e.g. `"~/src/dotfiles"`), which bypasses the project-root containment check
  2. Move the target repo inside the project directory and use a plain relative key like `"tools/repo"`
  3. Remove `..` segments from the key so it stays within the project root

Example fix

// before
[bootstrap.repos]
"../dotfiles" = { url = "https://github.com/jdx/dotfiles.git" }
// after
[bootstrap.repos]
"~/src/dotfiles" = { url = "https://github.com/jdx/dotfiles.git" }
Defensive patterns

Strategy: validation

Validate before calling

fn is_safe_relative_repo_path(p: &str) -> bool {
    let path = std::path::Path::new(p);
    !(path.is_absolute())
        && !path.components().any(|c| matches!(c, std::path::Component::ParentDir | std::path::Component::RootDir | std::path::Component::Prefix(_)))
}
// assert!(is_safe_relative_repo_path("tools/repo"))

Type guard

fn is_project_relative(p: &str) -> Option<&str> {
    let path = std::path::Path::new(p);
    if path.is_absolute() || p.starts_with('~') { return None; }
    if path.components().any(|c| !matches!(c, std::path::Component::Normal(_) | std::path::Component::CurDir)) { return None; }
    Some(p)
}

Prevention

When it happens

Trigger: Defining a bootstrap repo key like `"../shared/repo" = { url = ... }` in a project mise.toml; the relative path expands to a path containing a ParentDir component.

Common situations: Copying a repo entry from a global config into a project config without making it `~/`-relative or absolute; typos like `..\tools` on Windows; intentionally trying to check out a repo that lives in a sibling directory of the project.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/738ec9c87cdcf9c1. Report an issue: GitHub.

Appendix: source

Thrown at src/system/repos.rs:100

                "repo path `{path_raw}` cannot start with `~`; use `~/` for a home-relative path"
            );
        }
        let path = file::replace_path(&path_raw);
        let path = if path.is_absolute() {
            path
        } else {
            let Some(root) = project_root else {
                bail!(
                    "relative repo paths are only allowed in a project config; use an absolute path or a `~/` path"
                );
            };
            if path.components().any(|component| {
                matches!(
                    component,
                    Component::ParentDir | Component::RootDir | Component::Prefix(_)
                )
            }) {
                bail!(
                    "relative repo path `{path_raw}` must stay within the project root (no `..` or absolute segments)"
                );
            }
            if !path
                .components()
                .any(|component| matches!(component, Component::Normal(_)))
            {
                bail!(
                    "relative repo path `{path_raw}` must name a directory inside the project root"
                );
            }
            // Join only the Normal segments so `./foobar` resolves to
            // `<root>/foobar` rather than `<root>/./foobar` — a `.` component
            // survives `Path::join` and leaks into every displayed path.
            let mut resolved = root.to_path_buf();
            for component in path.components() {
                if let Component::Normal(segment) = component {
                    resolved.push(segment);

View on GitHub (pinned to 533346cc37)