jdx/mise · error

path must identify an output beneath the task directory

Error message

path must identify an output beneath the task directory

What it means

The final ensure_safe_relative check: a valid cache path must contain at least one Component::Normal. Paths like "." or strings that normalize to no real component do not identify an output beneath the task directory, so they are rejected even though they are relative and non-escaping.

Source

Thrown at src/task/task_cache.rs:1265

    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(root.join(expanded).to_str().unwrap_or_default())? {
                    let path = entry?;
                    glob_matched = true;
                    let rel = path.strip_prefix(root)?.to_path_buf();

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Name concrete output paths or globs instead of '.'
  2. Use outputs = [] plus explicit inclusion globs where possible
  3. If seen during restore, delete the cache entry — manifests written by mise never contain such roots

Example fix

# before
outputs = ["."]

# after
outputs = ["dist/**", "build/**"]
Defensive patterns

Strategy: validation

Validate before calling

python3 - <<'EOF'
import tomllib, sys, os
cfg = tomllib.load(open('mise.toml','rb'))
for name, t in cfg.get('tasks', {}).items():
    for o in map(str, t.get('outputs', [])):
        p = o.lstrip('\\').lstrip('!')
        if os.path.normpath(p) in ('.', ''):
            sys.exit(f'{name}: output must name a real path, not {o!r}')
print('ok')
EOF

Prevention

When it happens

Trigger: outputs = ["."] (cache the whole cwd as a single 'root' of '.'), or an output pattern that reduces to a bare current-directory component; also fires for malformed manifest roots read at restore time.

Common situations: Users trying to cache 'everything the task touched' with outputs = ["."]; templating that collapses to '.'; edge-case manifests produced by other tools.

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@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/2246c3dd229b3c4f. Report an issue: GitHub.