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 rejects absolute symlink targets in remote cache entries. Remote entries must store only relative symlinks so they can be resolved safely within the materialized output tree; absolute targets could point anywhere on the reading machine.
Solutions
- Change the task so it creates relative symlinks (e.g. ln -s ../lib/foo lib/foo) and republish the cache entry.
- Re-run the task with cache disabled/forced to produce a fresh, valid artifact.
- If symlinks to absolute locations are required, store them as regular files or relative links resolved at runtime instead.
- Purge the offending entry from the remote cache.
Example fix
// before (in task script) ln -s /opt/tools/bin/helper dist/helper // after cd dist && ln -s ../tools/bin/helper helper # or copy the binary instead of linking
Defensive patterns
Strategy: validation
Validate before calling
fn relative_link(t: &std::path::Path) -> bool {
!t.is_absolute()
} Type guard
fn is_relative_symlink_target(t: &std::path::Path) -> bool {
!t.is_absolute()
} Try / catch
match validate_cache_symlink_target(&link_path, &target) {
Err(e) if e.to_string().contains("must be relative") => {
log::warn!("skipping entry with absolute symlink {link_path:?} -> {target:?}");
}
other => other?,
} Prevention
- Create symlinks with relative targets inside build scripts (ln -s ../x y).
- Avoid symlink tasks that reference absolute system paths; copy files instead.
- Validate output trees for absolute symlinks before publishing to the cache.
- Keep link creation within the task's output directory.
When it happens
Trigger: archive_to_cas() or materialize_remote_tree() encounters a symlink whose target is absolute (starts with '/' or a Windows drive/prefix) (task_cache_store.rs:543).
Common situations: Build outputs containing absolute symlinks (e.g. created by ln -s /usr/lib/... during the task); archives created from system trees with absolute links; malicious cache entries.
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
- remote cache symlink target is unsafe
- remote cache path escapes its output root
- remote cache path must be relative
- remote cache symlink target escapes its output root
- a parent directory is now a symlink; left untouched
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/6d0246d2a66bea5a.
Report an issue: GitHub.
Appendix: 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 533346cc37)