{"record":{"id":"59edd4904e9928ad","repo":"jdx/mise","slug":"completion-target-is-not-an-owned-caskroom-symlink-59edd4","errorCode":null,"errorMessage":"completion target is not an owned Caskroom symlink: {}","messagePattern":"completion target is not an owned Caskroom symlink: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/state.rs","lineNumber":1132,"sourceCode":"    }\n    let completion_roots = [\n        CompletionShell::Bash,\n        CompletionShell::Fish,\n        CompletionShell::Zsh,\n        CompletionShell::Pwsh,\n    ]\n    .map(default_completion_dir);\n    for path in &receipt.completions {\n        let record = records\n            .get(path)\n            .ok_or_else(|| eyre!(\"missing completion target record\"))?;\n        if record.fingerprint.kind != CaskTargetKind::Symlink\n            || !completion_roots\n                .iter()\n                .any(|root| path_is_below(path, root))\n            || !symlink_resolves_below(path, &candidate.version_dir)\n        {\n            bail!(\n                \"completion target is not an owned Caskroom symlink: {}\",\n                path.display()\n            );\n        }\n    }\n    for record in &receipt.targets {\n        if !cask_target_record_matches(record)? {\n            bail!(\"artifact target has changed: {}\", record.path.display());\n        }\n    }\n    Ok(())\n}\n\npub(super) fn path_is_below(path: &Path, root: &Path) -> bool {\n    path.strip_prefix(root).is_ok_and(|relative| {\n        relative.components().next().is_some()\n            && !relative\n                .components()","sourceCodeStart":1114,"sourceCodeEnd":1150,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/cask/state.rs#L1114-L1150","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before: prune proceeds while user-managed completion file sits at the recorded path\n// after: verify and restore ownership before pruning\nlet meta = std::fs::symlink_metadata(&path)?;\nif !meta.file_type().is_symlink() {\n    // remove foreign file or drop the path from receipt.completions before pruning\n    std::fs::remove_file(&path)?;\n}","handlingStrategy":"validation","validationCode":"let meta = std::fs::symlink_metadata(&path)?;\nlet owned = meta.file_type().is_symlink()\n    && completion_roots.iter().any(|r| path_is_below(&path, r))\n    && symlink_resolves_below(&path, &candidate.version_dir);\nif !owned { /* drop from prune plan or restore before calling validate */ }","typeGuard":"fn is_owned_completion_symlink(path: &Path, roots: &[PathBuf], version_dir: &Path) -> bool {\n    std::fs::symlink_metadata(path).map(|m| m.file_type().is_symlink()).unwrap_or(false)\n        && roots.iter().any(|r| path_is_below(path, r))\n        && symlink_resolves_below(path, version_dir)\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["brew","cask","symlink","validation","prune"],"backgroundTag":"invalid-state-transition","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}