{"record":{"id":"a711072321e6fc15","repo":"jdx/mise","slug":"binary-target-is-not-an-owned-caskroom-symlink","errorCode":null,"errorMessage":"binary target is not an owned Caskroom symlink: {}","messagePattern":"binary target is not an owned Caskroom symlink: (.+?)","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":7066,"sourceCode":"            })\n        {\n            bail!(\n                \"app target is outside an allowed Applications directory: {}\",\n                path.display()\n            );\n        }\n    }\n    for path in &receipt.binaries {\n        let record = records\n            .get(path)\n            .ok_or_else(|| eyre!(\"missing binary target record\"))?;\n        if record.fingerprint.kind != CaskTargetKind::Symlink\n            || !allowed_binary_target_roots()\n                .iter()\n                .any(|root| path_is_below(path, root))\n            || !symlink_resolves_below(path, &candidate.version_dir)\n        {\n            bail!(\n                \"binary target is not an owned Caskroom symlink: {}\",\n                path.display()\n            );\n        }\n    }\n    for path in &receipt.fonts {\n        let record = records\n            .get(path)\n            .ok_or_else(|| eyre!(\"missing font target record\"))?;\n        let fonts = font_dir();\n        if record.fingerprint.kind != CaskTargetKind::File\n            || !path_is_below(path, &fonts)\n            || !path.strip_prefix(&fonts).is_ok_and(|relative| {\n                staged_target_matches(record, &candidate.version_dir.join(relative))\n            })\n        {\n            bail!(\n                \"font target is outside the platform font directory: {}\",","sourceCodeStart":7048,"sourceCodeEnd":7084,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L7048-L7084","documentation":"Binary targets are expected to be symlinks living under the brew prefix's binary roots whose resolution stays inside the cask's Caskroom version directory. The prune path only removes links that provably point at the version being deleted; a real file, a relocated link, or a link under an unexpected root fails this check.","triggerScenarios":"validate_cask_prune_candidate, binaries loop: fingerprint kind != Symlink, path not below allowed_binary_target_roots() (e.g. <prefix>/bin), or symlink_resolves_below(path, version_dir) false. Happens when the user replaced the symlink with a copied binary, relinked it to another cellar, or brew's own relink changed the target.","commonSituations":"User ran 'cp' over a stub or replaced a link with a real executable to 'fix' something; Homebrew reinstalled/linked its own version over the link; PATH shim tools rewiring the link; symlink target deleted first.","solutions":["Recreate the link so it points into the cask's Caskroom version dir: ln -sfn <prefix>/Caskroom/<token>/<version>/<binary> <prefix>/bin/<binary>","Reinstall the cask to restore both the staged artifact and the link, then prune","If the binary was deliberately replaced, uninstall the cask manually (remove the file and the Caskroom dir yourself)"],"exampleFix":"# before: link replaced by a real file\nls -l /opt/homebrew/bin/mytool   # regular file, not a symlink\n\n# after: point it back at the cask's staged artifact\nln -sfn /opt/homebrew/Caskroom/mytool/1.2.3/mytool /opt/homebrew/bin/mytool","handlingStrategy":"validation","validationCode":"// Before pruning, check each binary is a symlink resolving into its cask's\n// version dir.\nfn binary_owned_by_cask(path: &Path, version_dir: &Path) -> bool {\n    std::fs::symlink_metadata(path)\n        .is_ok_and(|m| m.file_type().is_symlink())\n        && symlink_resolves_below(path, version_dir)\n}","typeGuard":"fn is_owned_caskroom_symlink(path: &Path, version_dir: &Path) -> bool {\n    path.symlink_metadata().map(|m| m.file_type().is_symlink()).unwrap_or(false)\n        && symlink_resolves_below(path, version_dir)\n}","tryCatchPattern":"// Skipped-cask pattern from apply: warn and continue with the rest of the plan.\nif let Err(reason) = validate_cask_prune_candidate(candidate).and_then(|_| validate_cask_prune_claims(candidate)) {\n    warn!(\"brew-cask:{}: skipped because recorded artifacts changed after planning: {reason:#}\", candidate.token);\n    continue;\n}","preventionTips":["Never replace brew-managed symlinks in <prefix>/bin with copied files","If a link must move, recreate it pointing into Caskroom/<token>/<version>/","Reinstall the cask to restore canonical links before pruning"],"tags":["brew","cask","prune","symlink","integrity"],"backgroundTag":"symlink-integrity-check-failed","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}