jdx/mise · error

remote cache path must be relative

Error message

remote cache path must be relative

What it means

mise's remote task cache rejects cache entry paths that are empty or absolute before storing or materializing them. Remote cache paths are always interpreted relative to a per-task output root, so an absolute path would break the digest-addressed store and could write outside it. validate_cache_path runs before get, archive_to_cas, and materialize_remote_tree.

Source

Thrown at src/task/task_cache_store.rs:503

                action: action_digest,
                metadata: Some(metadata),
                output_root,
                version: 1,
            })
            .await
    }

    async fn remove(&self, _key: &str) -> Result<()> {
        // Ordinary cache writers intentionally have no remote-delete authority.
        Ok(())
    }

    fn touch(&self, _key: &str) {}
}

fn validate_cache_path(path: &Path) -> Result<()> {
    if path.as_os_str().is_empty() || path.is_absolute() {
        bail!("remote cache path must be relative");
    }
    if path.components().any(|component| {
        matches!(
            component,
            Component::ParentDir | Component::RootDir | Component::Prefix(_)
        )
    }) {
        bail!("remote cache path escapes its output root");
    }
    Ok(())
}

fn cache_name(path: &Path) -> Result<String> {
    let name = path
        .file_name()
        .and_then(|name| name.to_str())
        .ok_or_else(|| eyre!("remote cache paths must be valid UTF-8"))?;
    if name.is_empty() || name == "." || name == ".." || name.contains(['/', '\0']) {

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Make the path relative to the task's output root before passing it to the cache API.
  2. Check for a task definition where a cached output is specified as an absolute path and change it to a relative path.
  3. If you have an absolute path programmatically, strip the output root prefix (path.strip_prefix(root)?) before caching.

Example fix

// before
store.get(Path::new("/abs/build/out.bin"), &key).await?;
// after
let rel = Path::new("/abs/build/out.bin").strip_prefix("/abs/build")?;
store.get(rel, &key).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_cache_path(p: &Path) -> bool {
    !p.as_os_str().is_empty() && !p.is_absolute()
}
// call: assert!(is_valid_cache_path(&rel_path), "path must be relative");

Type guard

fn as_relative_cache_path(p: &Path) -> Option<&Path> {
    (!p.as_os_str().is_empty() && !p.is_absolute()).then_some(p)
}

Try / catch

match store.get(&rel, &key).await {
    Err(e) if e.to_string().contains("remote cache path must be relative") => {
        // fix path: strip the root prefix and retry
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling get, archive_to_cas (via commit), or materialize_remote_tree with a cache path that is empty (as_os_str().is_empty()) or absolute (Path::is_absolute, e.g. "/abs/out.bin" on Unix or "C:\\x" on Windows).

Common situations: Task [out] declarations or task configs that accidentally use an absolute path for a cached output; code that joins a root directory into the path before passing it to the cache API instead of passing the root-relative path.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/0b349b84eed475c4. Report an issue: GitHub.