jdx/mise · error
brew-cask: refusing to replace structured symlink directory
Error message
brew-cask: refusing to replace structured symlink directory '{}' What it means
A brew-cask structured symlink step resolved an individual link path to an existing real directory. The library unconditionally refuses to replace a directory with a symlink, because doing so could destroy user data — this is a hard safety check with no force override.
Source
Thrown at src/system/packages/brew/cask/flight.rs:756
create_flight_dir_all(&target, *sudo)?;
if created_external_directory || targets.previous_directories.contains(&target) {
targets.record_installed_directory(target.clone());
}
}
for source in sources {
let link = if target_is_dir {
let source_name = Path::new(&source).file_name().ok_or_else(|| {
eyre!("brew-cask: structured symlink source has no file name")
})?;
target.join(source_name)
} else {
target.clone()
};
let external = !link.starts_with(staged_path);
let link_metadata = link.symlink_metadata().ok();
if let Some(metadata) = &link_metadata {
if metadata.is_dir() {
bail!(
"brew-cask: refusing to replace structured symlink directory '{}'",
link.display()
);
}
if link.exists() && !force && !targets.previous_symlinks.contains(&link) {
bail!(
"brew-cask: structured symlink target '{}' already exists",
link.display()
);
}
if external {
targets.protect(&link)?;
} else if metadata.file_type().is_symlink() {
file::remove_file(&link)?;
} else {
file::remove_all(&link)?;
}
}View on GitHub (pinned to afd2eddd3a)
Solutions
- Move or delete the existing directory at the link path manually (after preserving any user data), then retry.
- Change the link target to a path that does not collide with a real directory.
- Rename the source artifact so the derived link name (target/<source file name>) no longer matches the directory.
Example fix
// before target: "~/Library/Application Support/MyApp" // exists as real dir // after target: "~/bin/myapp" // non-directory location
Defensive patterns
Strategy: validation
Validate before calling
let link = target.join(source_file_name);
if link.symlink_metadata().map(|m| m.is_dir()).unwrap_or(false) {
return Err(eyre!("refusing to replace real directory at {}", link.display()));
} Prevention
- Inspect the link destination for existing directories before defining the step.
- Avoid source file names that collide with common directory names (config/cache dirs).
- Remember there is no force override for real directories — resolve conflicts manually.
When it happens
Trigger: FlightStep::Symlink whose per-source link path (target, or target joined with the source's file name in multi-source mode) collides with an existing directory — e.g. a source file name matching an existing config/cache directory, or a target that is a real directory in single-source mode.
Common situations: Linking a CLI into a directory where a same-named directory exists (e.g. app support folders); a renamed upstream artifact now colliding with a real directory; linking into ~/Applications where a same-named .app directory exists as a real dir rather than a symlink.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- brew-cask: refusing to replace structured symlink directory
- brew-cask: staged symlink path escaped extraction root: {}
- brew-cask: refusing generic artifact source outside the extr
- brew-cask: refusing to restore flight target through a chang
- brew-cask: structured symlink source '{}' did not match any
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/5dddffd5be217008.
Report an issue: GitHub.