{"record":{"id":"c44fd92b0f210158","repo":"jdx/mise","slug":"copy-link-cannot-be-nested-below-a-symbolic-link","errorCode":null,"errorMessage":"copy_link cannot be nested below a symbolic link: {}","messagePattern":"copy_link cannot be nested below a symbolic link: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/remote.rs","lineNumber":854,"sourceCode":"                component,\n                Component::ParentDir | Component::RootDir | Component::Prefix(_)\n            )\n        })\n    {\n        bail!(\n            \"copy_link must be a relative path within the source: {}\",\n            link.display()\n        );\n    }\n    let mut parent = source.to_path_buf();\n    for component in link.parent().unwrap_or_else(|| Path::new(\"\")).components() {\n        if let Component::Normal(component) = component {\n            parent.push(component);\n            let metadata = fs::symlink_metadata(&parent).wrap_err_with(|| {\n                format!(\"copy_link parent does not exist: {}\", parent.display())\n            })?;\n            if metadata.file_type().is_symlink() {\n                bail!(\n                    \"copy_link cannot be nested below a symbolic link: {}\",\n                    link.display()\n                );\n            }\n        }\n    }\n    let path = source.join(link);\n    let metadata = fs::symlink_metadata(&path)\n        .wrap_err_with(|| format!(\"copy_link does not exist: {}\", link.display()))?;\n    if !metadata.file_type().is_symlink() {\n        bail!(\"copy_link is not a symbolic link: {}\", link.display());\n    }\n    Ok(())\n}\n\nfn materialize_link(source: &Path, staged_source: &Path, link: &Path) -> Result<()> {\n    validate_copy_link_path(staged_source, link)\n        .wrap_err_with(|| format!(\"staged copy_link is unsafe: {}\", link.display()))?;","sourceCodeStart":836,"sourceCodeEnd":872,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/remote.rs#L836-L872","documentation":"While validating a copy_link, mise walks each parent component of the link using symlink_metadata and rejects the link if any intermediate component is itself a symlink. Resolving through a symlinked directory would make the final location ambiguous and could escape the staged source, so it fails early.","triggerScenarios":"The configured copy_link has a parent directory that is a symbolic link inside the staged source (e.g. link = \"bin/tool\" where bin is a symlink to ../scripts).","commonSituations":"Projects that restructure directories with symlinks (bin -> scripts/bin) then add copy_link entries; also generated layouts where CI creates symlinked folders before staging.","solutions":["Point copy_link at a path whose parent directories are real directories","Replace the intermediate symlink with a real directory (or copy its contents)","Adjust the link to target the symlink's final destination directly"],"exampleFix":"// before\ncopy_link = \"bin/tool\" // bin is a symlink\n// after\ncopy_link = \"scripts/bin/tool\"","handlingStrategy":"validation","validationCode":"function parentIsRealDir(root, rel) {\n  let cur = root;\n  for (const seg of path.posix.dirname(rel).split('/').filter(Boolean)) {\n    cur = path.join(cur, seg);\n    const st = fs.lstatSync(cur);\n    if (st.isSymbolicLink()) return false;\n  }\n  return true;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Avoid symlinked intermediate directories inside staged sources","Replace symlinked dirs with real directories before staging","Add a CI check that lstat on each copy_link parent is not a symlink"],"tags":["symlink","path","validation","remote"],"backgroundTag":"invalid-argument-value","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}