jdx/mise · error

remote cache symlink target must be relative

Error message

remote cache symlink target must be relative

What it means

validate_cache_symlink_target() is applied to every symlink mise stores in or restores from the task cache - both tar archive entries on upload and remote directory proto entries on restore. The first rule is that the target must be a relative path; an absolute target like "/usr/bin/tool" bails with 'remote cache symlink target must be relative'.

Source

Thrown at src/task/task_cache_store.rs:543

}

fn validate_cache_name(name: &str) -> Result<()> {
    let path = Path::new(name);
    if name.is_empty()
        || name == "."
        || name == ".."
        || name.contains(['/', '\\', '\0'])
        || path.components().count() != 1
        || !matches!(path.components().next(), Some(Component::Normal(_)))
    {
        bail!("invalid remote cache path component");
    }
    Ok(())
}

fn validate_cache_symlink_target(path: &Path, target: &Path) -> Result<()> {
    if target.is_absolute() {
        bail!("remote cache symlink target must be relative");
    }
    let resolved = path.parent().unwrap_or(Path::new("")).join(target);
    let mut depth = 0_i64;
    for component in resolved.components() {
        match component {
            Component::Normal(_) => depth += 1,
            Component::ParentDir => depth -= 1,
            Component::CurDir => {}
            _ => bail!("remote cache symlink target is unsafe"),
        }
        if depth < 0 {
            bail!("remote cache symlink target escapes its output root");
        }
    }
    Ok(())
}

fn archive_to_cas(path: &Path, staging_dir: &Path) -> Result<(CacheDigest, Vec<BlobUpload>)> {

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Create the symlink with a target relative to the link's location
  2. Exclude outputs containing absolute symlinks from the task's cache output roots
  3. Delete the cached entry so the next run rebuilds it without the offending link
  4. In scripts, prefer relative ln -s targets or copy the file instead

Example fix

# before
ln -s /opt/toolchain/bin/gcc ./gcc
# after (relative to the link's location)
ln -s ../toolchain/bin/gcc ./gcc
Defensive patterns

Strategy: validation

Validate before calling

fn output_tree_has_only_relative_symlinks(root: &Path) -> std::io::Result<bool> {
    for entry in walkdir::WalkDir::new(root).follow_links(false) {
        let entry = entry?;
        if entry.file_type().is_symlink()
            && std::fs::read_link(entry.path())?.is_absolute()
        {
            return Ok(false);
        }
    }
    Ok(true)
}

assert!(output_tree_has_only_relative_symlinks(&out_dir)?);

Type guard

fn is_relative_symlink_target(link: &std::path::Path) -> bool {
    std::fs::read_link(link)
        .map(|t| !t.is_absolute())
        .unwrap_or(true)
}

Try / catch

match cache_commit(&task).await {
    Ok(()) => (),
    Err(err) if err.to_string().contains("symlink target must be relative") => {
        eprintln!("output contains an absolute symlink; excluding from cache");
        narrow_cache_roots(&task) // then rebuild
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: A task output contains a symlink created with an absolute target (ln -s /absolute/path link) and the task gets cached: upload goes through archive_to_cas -> validate_cache_symlink_target; restore hits the same check on a directory proto's symlink target.

Common situations: Build tasks creating convenience symlinks to system tools or /tmp paths; node_modules/.bin-style links made with absolute paths; CI caches created on one machine and restored on another where the absolute path does not exist.

Related errors


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