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(¤t) {
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
- Point outputs at the real (non-symlink) target, e.g. the versioned path instead of the 'current' link
- Replace the symlinked directory with a real directory before the task produces outputs
- 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
- Create output directories with mkdir -p, never ln -s in the ancestor chain
- Audit dev-shell/nix environments that replace build dirs with symlinks
- Point outputs at versioned real paths, not 'current'/'latest' links
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
- brew-cask: staged symlink path escaped extraction root: {}
- path must not escape the task directory
- remote cache path escapes its output root
- remote cache symlink target must be relative
- remote cache symlink target is unsafe
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/442b365430b08eab.
Report an issue: GitHub.