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

  1. Move or delete the existing directory at the link path manually (after preserving any user data), then retry.
  2. Change the link target to a path that does not collide with a real directory.
  3. 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

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


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/5dddffd5be217008. Report an issue: GitHub.