jdx/mise · error
copy_link is not a symbolic link: {}
Error message
copy_link is not a symbolic link: {} What it means
After confirming the copy_link path exists in the source, mise requires that the entry actually be a symbolic link, since the remote staging step re-materializes symlinks. A regular file or directory given as copy_link fails with this error.
Source
Thrown at src/system/remote.rs:865
for component in link.parent().unwrap_or_else(|| Path::new("")).components() {
if let Component::Normal(component) = component {
parent.push(component);
let metadata = fs::symlink_metadata(&parent).wrap_err_with(|| {
format!("copy_link parent does not exist: {}", parent.display())
})?;
if metadata.file_type().is_symlink() {
bail!(
"copy_link cannot be nested below a symbolic link: {}",
link.display()
);
}
}
}
let path = source.join(link);
let metadata = fs::symlink_metadata(&path)
.wrap_err_with(|| format!("copy_link does not exist: {}", link.display()))?;
if !metadata.file_type().is_symlink() {
bail!("copy_link is not a symbolic link: {}", link.display());
}
Ok(())
}
fn materialize_link(source: &Path, staged_source: &Path, link: &Path) -> Result<()> {
validate_copy_link_path(staged_source, link)
.wrap_err_with(|| format!("staged copy_link is unsafe: {}", link.display()))?;
let source_link = source.join(link);
let staged_link = staged_source.join(link);
let target = fs::canonicalize(&source_link)
.wrap_err_with(|| format!("failed to resolve copy_link {}", link.display()))?;
let parent = staged_link
.parent()
.expect("a validated copy_link has a staged parent");
let original_permissions = make_directory_writable(parent)?;
let result = (|| {
crate::file::remove_file(&staged_link)?;
if target.is_dir() {View on GitHub (pinned to afd2eddd3a)
Solutions
- Restore the entry to be a symbolic link (ln -s <target> <path>)
- Remove the copy_link entry if the file is now a real file and no longer needs replication
- On Windows/checkouts with symlinks disabled, enable symlink support in git and re-checkout
Example fix
// before: bin/tool is a regular file copy_link = "bin/tool" // after rm bin/tool && ln -s scripts/tool bin/tool
Defensive patterns
Strategy: validation
Validate before calling
function isSymlink(root, rel) {
try { return fs.lstatSync(path.join(root, rel)).isSymbolicLink(); } catch { return false; }
} Type guard
const isSymlink = (p) => { try { return fs.lstatSync(p).isSymbolicLink(); } catch { return false; } }; Try / catch
try { await stageRemote(); } catch (e) { if (String(e).includes('copy_link is not a symbolic link')) { recreateSymlinks(); } throw e; } Prevention
- Keep git core.symlinks enabled and checkouts symlink-faithful
- Verify copy_link targets with lstat (not stat) before deployment
- Re-create symlinks after vendoring or dependency rewrites
When it happens
Trigger: copy_link names a path that exists but is a regular file or directory, checked via symlink_metadata in validate_copy_link_path before materialize_link runs.
Common situations: Config drift: the entry used to be a symlink but was replaced by a real file after vendoring dependencies, or a checkout option (e.g. git config core.symlinks=false) turned symlinks into plain files.
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
- copy_link must be a relative path within the source: {}
- copy_link cannot be nested below a symbolic link: {}
- remote mise path does not name an executable: {command:?}
- relative remote mise path escapes the staged project: {comma
- relative remote mise path does not name an executable: {comm
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/e0359af0d141dda3.
Report an issue: GitHub.