jdx/mise · error
completion target is not an owned Caskroom symlink
Error message
completion target is not an owned Caskroom symlink: {} What it means
During cask prune validation, each recorded shell-completion target must still be a symlink whose path sits under one of the default completion directories (bash/fish/zsh/pwsh) and whose link target resolves below the candidate version directory. This guards prune operations against deleting or relinking completions that are no longer owned by this cask's staged version. If any of those three invariants fails, the prune is refused rather than corrupting another package's completion files.
Solutions
- Inspect the reported path: if it is no longer owned by the cask, remove it from the prune candidate or fix the receipt so only owned completions are listed.
- Restore the symlink so it points below the candidate version_dir (relink to <version_dir>/... under a default completion directory).
- Reinstall or re-stage the cask so the completion targets and their recorded fingerprints are regenerated consistently.
Example fix
// before: prune proceeds while user-managed completion file sits at the recorded path
// after: verify and restore ownership before pruning
let meta = std::fs::symlink_metadata(&path)?;
if !meta.file_type().is_symlink() {
// remove foreign file or drop the path from receipt.completions before pruning
std::fs::remove_file(&path)?;
} Defensive patterns
Strategy: validation
Validate before calling
let meta = std::fs::symlink_metadata(&path)?;
let owned = meta.file_type().is_symlink()
&& completion_roots.iter().any(|r| path_is_below(&path, r))
&& symlink_resolves_below(&path, &candidate.version_dir);
if !owned { /* drop from prune plan or restore before calling validate */ } Type guard
fn is_owned_completion_symlink(path: &Path, roots: &[PathBuf], version_dir: &Path) -> bool {
std::fs::symlink_metadata(path).map(|m| m.file_type().is_symlink()).unwrap_or(false)
&& roots.iter().any(|r| path_is_below(path, r))
&& symlink_resolves_below(path, version_dir)
} Prevention
- Do not manually replace generated completion symlinks with copied files.
- Keep completions under the default per-shell completion directories.
- Reinstall/relink the cask instead of hand-editing its symlink targets after version changes.
When it happens
Trigger: Calling validate_cask_prune_candidate (via cask_prune_plan_from_tokens or apply_cask_prune_plan_in) when a receipt.completions entry is not a symlink (CaskTargetKind changed), was moved outside the default completion dirs, or now points to a directory outside the candidate version_dir (e.g. user replaced the symlink with a real file or a custom target).
Common situations: A user manually replaced a generated completion symlink with a copied file; a package update relocated completions to a non-default directory; another tool or dotfiles manager (e.g. oh-my-zsh, fisher) owns the completion path; the cask's version directory was renamed so the symlink dangles or points elsewhere.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- binary target is not an owned Caskroom symlink
- app target is outside an allowed Applications directory
- artifact target has changed
- binary target is not an owned Caskroom symlink
- brew-cask: binary artifact
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/59edd4904e9928ad.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/packages/brew/cask/state.rs:1132
}
let completion_roots = [
CompletionShell::Bash,
CompletionShell::Fish,
CompletionShell::Zsh,
CompletionShell::Pwsh,
]
.map(default_completion_dir);
for path in &receipt.completions {
let record = records
.get(path)
.ok_or_else(|| eyre!("missing completion target record"))?;
if record.fingerprint.kind != CaskTargetKind::Symlink
|| !completion_roots
.iter()
.any(|root| path_is_below(path, root))
|| !symlink_resolves_below(path, &candidate.version_dir)
{
bail!(
"completion target is not an owned Caskroom symlink: {}",
path.display()
);
}
}
for record in &receipt.targets {
if !cask_target_record_matches(record)? {
bail!("artifact target has changed: {}", record.path.display());
}
}
Ok(())
}
pub(super) fn path_is_below(path: &Path, root: &Path) -> bool {
path.strip_prefix(root).is_ok_and(|relative| {
relative.components().next().is_some()
&& !relative
.components()View on GitHub (pinned to 533346cc37)