jdx/mise · error

output path {} traverses symlink ancestor {}

Error message

output path {} traverses symlink ancestor {}

What it means

Before packing outputs into the cache archive, mise walks each output path's ancestor directories; if any ancestor is a symbolic link, it refuses the path. Symlinked ancestors make archive paths ambiguous and could redirect restores outside the intended tree, so this is a hard security stop, not a warning.

Source

Thrown at src/task/task_cache.rs:1331

    roots.sort_by_key(|path| path.components().count());
    let mut result = Vec::<PathBuf>::new();
    for root in roots {
        if !result.iter().any(|parent| root.starts_with(parent)) {
            result.push(root);
        }
    }
    result.sort();
    result
}

fn ensure_no_symlink_ancestors(root: &Path, rel: &Path) -> Result<()> {
    let mut current = root.to_path_buf();
    let component_count = rel.components().count();
    for component in rel.components().take(component_count.saturating_sub(1)) {
        current.push(component);
        match fs::symlink_metadata(&current) {
            Ok(metadata) if metadata.file_type().is_symlink() => {
                bail!(
                    "output path {} traverses symlink ancestor {}",
                    rel.display(),
                    current.display()
                );
            }
            Ok(_) => {}
            Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
            Err(err) => return Err(err.into()),
        }
    }
    Ok(())
}

fn install_transactionally(
    root: &Path,
    staging: &Path,
    install_roots: &[PathBuf],
    remove_roots: &[PathBuf],

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Point outputs at the real (non-symlink) target, e.g. the versioned path instead of the 'current' link
  2. Replace the symlinked directory with a real directory before the task produces outputs
  3. Create output directories with mkdir -p rather than ln -s anywhere in the ancestor chain

Example fix

# before: 'current' is a symlink to 'v1.2.3'
outputs = ['current/dist/app']

# after: reference the real directory
outputs = ['v1.2.3/dist/app']
Defensive patterns

Strategy: validation

Validate before calling

# detect symlinked ancestors of an output path
python3 - <<'PY'
import os, sys
p = sys.argv[1].rstrip(os.sep)
while p not in ('', os.sep, '.'):
    if os.path.islink(p):
        print('symlink ancestor:', p)
    p = os.path.dirname(p)
PY dist/bin/tool

Prevention

When it happens

Trigger: ensure_no_symlink_ancestors walks the parent components of a relative output and fs::symlink_metadata reports one of them as a symlink — e.g. outputs = ['dist/bin/tool'] where dist or dist/bin is a symlink ('current' -> 'v1.2.3' style).

Common situations: Build dirs managed as symlinks (nix-style /current, shared caches); CI creating a versioned dir plus a 'latest' link; monorepos linking package dirs.

Related errors


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