jdx/mise · error

invalid opt record for brew

Error message

invalid opt record for brew:{name} at {}: target {} must be a directory directly inside {}; inspect it and restore the formula's correct opt link

What it means

After confirming `opt/<formula>` is a symlink, strict_package_root canonicalizes it and requires the target to be a directory whose parent is exactly the formula's Cellar rack (`Cellar/<formula>`). This proves the opt link points at a real keg inside this prefix's Cellar. A dangling target, a target outside Cellar, or a versioned directory elsewhere (e.g. into another prefix, or a file) makes the record invalid.

Solutions

  1. Inspect the link target: `readlink -f $(brew --prefix)/opt/<name>` and compare with `ls $(brew --prefix)/Cellar/<name>/`
  2. Remove and recreate the link to the keg actually in Cellar: `ln -sfn ../Cellar/<name>/<version> $(brew --prefix)/opt/<name>`
  3. If the keg is genuinely missing, re-apply the package: `mise bootstrap packages apply brew:<name>`

Example fix

# before: opt/python -> /opt/old-brew/Cellar/python/3.12 (foreign prefix)
ln -sfn "../Cellar/python/3.13.2" "$(brew --prefix)/opt/python"
Defensive patterns

Strategy: validation

Validate before calling

t=$(readlink -f "$(brew --prefix)/opt/$name")
case "$t" in
  "$(brew --prefix)/Cellar/$name"/*) ;;
  *) echo "opt/$name points outside Cellar/$name: $t" ;;
esac

Prevention

When it happens

Trigger: The opt symlink resolves but its canonicalized target is not a directory directly inside Cellar/<name> — e.g. link points into another Homebrew prefix, a relocated/stale path, a version directory that was moved, or a file instead of a keg directory.

Common situations: Prefixes migrated or symlinked between locations leaving stale absolute targets; manually relinking opt to an extracted tarball outside Cellar; upgrading tools that moved the keg.

Related errors


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

Appendix: source

Thrown at src/system/packages/brew/pour.rs:84

    };
    let opt = prefix.join("opt").join(name);
    let rack = prefix.join("Cellar").join(name);
    let metadata =
        std::fs::symlink_metadata(&opt).map_err(|err| package_root_io_error(name, &opt, err))?;
    if !metadata.is_symlink() {
        bail!(
            "invalid opt record for brew:{name} at {}: expected a symbolic link; inspect it and restore the formula's correct opt link",
            opt.display()
        );
    }
    let target =
        std::fs::canonicalize(&opt).map_err(|err| package_root_io_error(name, &opt, err))?;
    let canonical_rack =
        std::fs::canonicalize(&rack).map_err(|err| package_root_io_error(name, &rack, err))?;
    let metadata =
        std::fs::metadata(&target).map_err(|err| package_root_io_error(name, &target, err))?;
    if !metadata.is_dir() || target.parent() != Some(canonical_rack.as_path()) {
        bail!(
            "invalid opt record for brew:{name} at {}: target {} must be a directory directly inside {}; inspect it and restore the formula's correct opt link",
            opt.display(),
            target.display(),
            canonical_rack.display()
        );
    }
    Ok(opt)
}

/// Retain the filesystem error while adding lookup context and actionable recovery guidance.
fn package_root_io_error(name: &str, path: &Path, err: std::io::Error) -> eyre::Report {
    let context = if err.kind() == std::io::ErrorKind::NotFound {
        format!(
            "brew:{name} has no usable installed opt link: {}; install or reconcile it with `mise bootstrap packages apply brew:{name}`",
            path.display()
        )
    } else {
        format!(

View on GitHub (pinned to 533346cc37)