jdx/mise · error

binary target is not an owned Caskroom symlink

Error message

binary target is not an owned Caskroom symlink: {}

What it means

Cask binary targets must be symlinks living under an allowed binary target root (e.g. a Homebrew bin dir) that resolve back into the cask's staged version directory. If a recorded binary target is not a Symlink-kind record, sits outside allowed roots, or its symlink no longer resolves below candidate.version_dir, mise refuses to prune — deleting would either break a foreign link or remove a file mise does not own.

Solutions

  1. Reinstall the cask with mise so the binary symlink is recreated pointing into the correct version dir: mise uninstall brew-cask:foo && mise install brew-cask:foo
  2. Inspect the binary path (ls -l) and restore it to a symlink resolving into the cask's Caskroom version directory before pruning
  3. Ensure the binary lives under an allowed binary root for the active Homebrew prefix; align HOMEBREW_PREFIX between the installing and pruning environments
  4. Leave that cask out of the prune plan and manage its binaries via brew

Example fix

// before: symlink replaced by a copied binary
$ rm /usr/local/bin/foo && cp ~/staging/foo /usr/local/bin/foo
// after: restore owned symlink into the version dir
ln -sf "$(mise --prefix brew-cask:foo)/Caskroom/foo/1.2.3/foo.bin" /usr/local/bin/foo
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight: each binary target must be a symlink into the version dir
fn binaries_are_owned_symlinks(receipt: &CaskReceipt, version_dir: &Path) -> bool {
    let roots = allowed_binary_target_roots();
    receipt.binaries.iter().all(|p| {
        p.symlink_metadata().map(|m| m.file_type().is_symlink()).unwrap_or(false)
            && roots.iter().any(|r| path_is_below(p, r))
            && symlink_resolves_below(p, version_dir)
    })
}

Type guard

fn is_owned_caskroom_symlink(path: &Path, version_dir: &Path) -> bool {
    path.symlink_metadata().map(|m| m.file_type().is_symlink()).unwrap_or(false)
        && symlink_resolves_below(path, version_dir)
}

Try / catch

match apply_cask_prune_plan_in(&plan) {
    Ok(removed) => info!("pruned {removed} casks"),
    Err(e) if e.to_string().contains("not an owned Caskroom symlink") => {
        warn!("binary link replaced externally; reinstalling cask: {e:#}");
        // recreate the symlink or fall back to brew management
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling cask_prune_plan_from_tokens or apply_cask_prune_plan_in when a receipt.binaries path was replaced by a real file (not a symlink), the symlink points outside the cask's version_dir (e.g. into a different version or a manually managed location), or the binary was installed to a nonstandard prefix outside allowed_binary_target_roots().

Common situations: User replaced the symlink with a copied binary or their own wrapper script; brew or the user relinked the binary to another cask version; PATH tooling rewrote links in /usr/local/bin; custom HOMEBREW_PREFIX so binaries sit outside the roots this mise recognizes.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/89409f3a9cd01ad4. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/cask/state.rs:1092

            })
        {
            bail!(
                "app target is outside an allowed Applications directory: {}",
                path.display()
            );
        }
    }
    for path in &receipt.binaries {
        let record = records
            .get(path)
            .ok_or_else(|| eyre!("missing binary target record"))?;
        if record.fingerprint.kind != CaskTargetKind::Symlink
            || !allowed_binary_target_roots()
                .iter()
                .any(|root| path_is_below(path, root))
            || !symlink_resolves_below(path, &candidate.version_dir)
        {
            bail!(
                "binary target is not an owned Caskroom symlink: {}",
                path.display()
            );
        }
    }
    for path in &receipt.fonts {
        let record = records
            .get(path)
            .ok_or_else(|| eyre!("missing font target record"))?;
        let fonts = font_dir();
        if record.fingerprint.kind != CaskTargetKind::File
            || !path_is_below(path, &fonts)
            || !path.strip_prefix(&fonts).is_ok_and(|relative| {
                staged_target_matches(record, &candidate.version_dir.join(relative))
            })
        {
            bail!(
                "font target is outside the platform font directory: {}",

View on GitHub (pinned to 533346cc37)