jdx/mise · error
path must not escape the task directory
Error message
path must not escape the task directory
What it means
mise validates task cache output paths with ensure_safe_relative, which rejects any relative path containing ParentDir (..), RootDir (/), or Windows Prefix components. This prevents a task's cache outputs from referencing or overwriting files outside the task's working directory. It is a deliberate path-traversal guard applied to each `outputs` entry in a task definition.
Solutions
- Make the output path relative and inside the task directory (e.g. `dist/` instead of `../dist`)
- If outputs genuinely live outside the task dir, relocate them into the task dir or change the task's dir
- Remove leading `/` or drive prefixes and any `..` components from the pattern
Example fix
# before [task.build] outputs = ['../shared/dist'] # after [task.build] outputs = ['dist']
Defensive patterns
Strategy: validation
Validate before calling
fn is_safe_output(p: &str) -> bool {
let path = std::path::Path::new(p);
!path.is_empty() && !path.is_absolute()
&& !path.components().any(|c| matches!(c,
std::path::Component::ParentDir
| std::path::Component::RootDir
| std::path::Component::Prefix(_)))
} Type guard
fn safe_output(p: &str) -> Option<&str> {
is_safe_output(p).then_some(p)
} Prevention
- Always declare outputs relative to the task's dir
- Never start output paths with / or a drive letter
- Avoid .. in generated output lists
When it happens
Trigger: Declaring a task output like `outputs = ['../build']`, an absolute path like `/tmp/out`, or a Windows drive path (`C:\out`) in a task with cache outputs; the check runs when validating/resolving task cache outputs.
Common situations: Authors moving outputs to a shared directory outside the task dir; copying an absolute path from a build log; Windows users using drive-letter paths; typos like `..foo/..`.
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
- path must identify an output beneath the task directory
- bootstrap_command returned an unsafe mise path
- brew-cask: command_wrapper requires a command name without…
- install_mise must be an absolute path or start with ~/
- install_mise must name an executable file
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/641245a542f670b1.
Report an issue: GitHub.
Appendix: source
Thrown at src/task/task_cache.rs:1261
)
})?;
}
build_output_matcher(root, &patterns)
.wrap_err_with(|| format!("task {} has an invalid cache output pattern", task.name))?;
Ok(())
}
fn ensure_safe_relative(path: &Path) -> Result<()> {
if path.as_os_str().is_empty() || path.is_absolute() {
bail!("path must be a non-empty relative path");
}
if path.components().any(|c| {
matches!(
c,
Component::ParentDir | Component::RootDir | Component::Prefix(_)
)
}) {
bail!("path must not escape the task directory");
}
if !path.components().any(|c| matches!(c, Component::Normal(_))) {
bail!("path must identify an output beneath the task directory");
}
Ok(())
}
fn resolve_output_roots(task: &Task, root: &Path, require_matches: bool) -> Result<Vec<PathBuf>> {
let mut resolved = BTreeSet::new();
let patterns = task.outputs.patterns();
let matcher = build_output_matcher(root, &patterns)?;
for output in output_glob_patterns(&patterns) {
ensure_safe_relative(Path::new(&output))?;
if crate::task::task_source_checker::is_glob_pattern(&output) {
let mut glob_matched = false;
for expanded in expand_enumeration_patterns(&output)? {
ensure_safe_relative(Path::new(&expanded))?;
for entry in glob_walk(&root.join(expanded), false)? {View on GitHub (pinned to 533346cc37)