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
- Create the symlink with a target relative to the link's location
- Exclude outputs containing absolute symlinks from the task's cache output roots
- Delete the cached entry so the next run rebuilds it without the offending link
- 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
- Always create cached-output symlinks with targets relative to the link location
- Lint task outputs for absolute symlinks before enabling remote cache in CI
- Prefer copying binaries over symlinking them into cached output roots
- Remember caches restore on other machines: absolute targets are wrong even when they pass
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
- remote cache symlink target escapes its output root
- remote cache path escapes its output root
- remote cache symlink target is unsafe
- path must not escape the task directory
- output path {} traverses symlink ancestor {}
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/6d0246d2a66bea5a.
Report an issue: GitHub.