jdx/mise · error
brew-cask: structured copy target '{}' already exists
Error message
brew-cask: structured copy target '{}' already exists What it means
The copy step's target already exists (`symlink_metadata()` succeeds, which includes dangling symlinks) and the step does not set `overwrite: true`. Rather than clobber the existing file, the step validates the conflict up front and aborts; with `overwrite` it would instead protect (external targets) or remove (staged targets) the existing entry first.
Source
Thrown at src/system/packages/brew/cask.rs:3579
let sources = flight_symlink_sources(cask, source, *source_glob, staged_path, appdir)?;
let [source] = sources.as_slice() else {
bail!("brew-cask: structured copy source must resolve to exactly one path");
};
if !source.exists() {
bail!(
"brew-cask: structured copy source '{}' was not found",
source.display()
);
}
if source.is_dir() && !recursive {
bail!("brew-cask: structured directory copy requires recursive=true");
}
let target = resolve_flight_path_with_context(cask, target, staged_path, appdir)?;
let external = !target.starts_with(staged_path);
let target_metadata = target.symlink_metadata().ok();
if target_metadata.is_some() {
if !overwrite {
bail!(
"brew-cask: structured copy target '{}' already exists",
target.display()
);
}
if external {
targets.protect(&target)?;
} else {
file::remove_all(&target)?;
}
}
if let Some(parent) = target.parent() {
create_dir_all_elevating(parent)?;
}
if external && target_metadata.is_none() {
// Bind an absent target to its resolved parent only after
// creating that parent so rollback can validate its identity.
targets.protect(&target)?;
}View on GitHub (pinned to 6f52dcdf99)
Solutions
- Set `overwrite: true` on the copy step when replacing the target is intended
- Ensure the previous version's uninstall removed the target (receipts/rollback) before reinstalling
- Retarget the copy to a cask-namespaced path that cannot collide with other files
Example fix
# before
copy(source: "bin/tool", target: "#{staged}/bin/tool")
# after
copy(source: "bin/tool", target: "#{staged}/bin/tool", overwrite: true) Defensive patterns
Strategy: validation
Validate before calling
// Before executing a copy step, detect target conflicts the step would refuse
let target = resolve_flight_path_with_context(cask, &step.target, staged_path, appdir)?;
if target.symlink_metadata().is_ok() && !step.overwrite {
return Err(eyre!("copy target '{}' exists; set overwrite or remove it first", target.display()));
} Try / catch
if err.to_string().contains("copy target") && err.to_string().contains("already exists") {
// prompt to remove the target or set overwrite, then retry the install
} Prevention
- Run the previous version's uninstall before reinstalling or upgrading
- Set `overwrite: true` on copy steps whose targets are cask-owned and disposable
- Namespace copy targets under the cask token to avoid collisions
When it happens
Trigger: Re-running an install whose previous run already created the target; two artifact steps writing the same target path; a user-created file at the target location.
Common situations: Reinstall or upgrade after an incomplete uninstall; converting a move step to a copy step without cleanup; name collision under the appdir with an existing app.
Related errors
- brew-cask: structured copy source must resolve to exactly on
- brew-cask: structured copy source '{}' was not found
- brew-cask: structured directory copy requires recursive=true
- brew-cask: structured move with multiple sources requires a
- brew-cask: structured symlink source '{}' did not match any
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/7e1423c54419141d.
Report an issue: GitHub.