jdx/mise · error

font target is outside the platform font directory

Error message

font target is outside the platform font directory: {}

What it means

Font targets in a cask receipt must be File-kind records located inside the platform font directory (~/Library/Fonts on macOS) whose contents match the staged copy in the version dir (checked by stripping the font-dir prefix and comparing fingerprints). If the font path is outside the font directory or the staged file no longer matches, mise refuses to prune the cask to avoid deleting fonts it cannot prove it owns.

Solutions

  1. Reinstall the cask with mise (uninstall + install) so font paths and fingerprints are re-recorded against the current font directory
  2. Restore the font to its recorded path under the platform font directory, or update the environment so font_dir() matches where fonts were installed
  3. Replace the modified font file with the original so the staged fingerprint matches, then retry the prune
  4. Skip pruning this cask and remove fonts manually or via brew

Example fix

// before: font moved out of the font dir, fingerprint mismatch
~/Fonts/Custom/Foo.ttf  // not below font_dir() as recorded
// after: restore location and reinstall to regenerate receipt
mv ~/Fonts/Custom/Foo.ttf ~/Library/Fonts/Foo.ttf
mise uninstall brew-cask:foo && mise install brew-cask:foo
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight: font targets must be files under the platform font dir
fn fonts_in_font_dir(receipt: &CaskReceipt) -> bool {
    let fonts = font_dir();
    receipt.fonts.iter().all(|p| {
        path_is_below(p, &fonts)
            && p.metadata().map(|m| m.is_file()).unwrap_or(false)
    })
}

Type guard

fn font_target_is_safe(path: &Path, fonts: &Path) -> bool {
    path.is_file()
        && path_is_below(path, fonts)
        && path.strip_prefix(fonts).is_ok()
}

Try / catch

match apply_cask_prune_plan_in(&plan) {
    Ok(removed) => info!("pruned {removed} casks"),
    Err(e) if e.to_string().contains("outside the platform font directory") => {
        warn!("font moved or modified; reinstalling cask: {e:#}");
        // restore font location or fall back to manual removal
    }
    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.fonts path is not below font_dir() (e.g. font moved to a custom fontdir), the record's kind is not File, or the staged font under candidate.version_dir differs from the installed file (edited, replaced, or re-downloaded variant).

Common situations: User moved a cask font into a custom fonts directory via HOMEBREW_CASK_OPTS --fontdir; the font file was modified or replaced after install so fingerprints diverge; receipts written under a different fontdir setting than the pruning run uses.

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/fa016d463fa63232. Report an issue: GitHub.

Appendix: source

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

        {
            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: {}",
                path.display()
            );
        }
    }
    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

View on GitHub (pinned to 533346cc37)