jdx/mise · error

invalid opt record for brew

Error message

invalid opt record for brew:{name} at {}: expected a symbolic link; inspect it and restore the formula's correct opt link

What it means

Homebrew maintains `opt/<formula>` as a symlink to the active keg under Cellar. strict_package_root requires this record to be a symbolic link; if `opt/<name>` exists as a regular file, directory, or other non-symlink entry, mise treats the install record as corrupt and refuses to use (or silently repair) it, telling you to restore the correct opt link.

Solutions

  1. Inspect the path: `ls -la $(brew --prefix)/opt/<name>` to see what replaced the symlink
  2. Remove the bogus entry and recreate the link: `rm -rf $(brew --prefix)/opt/<name> && ln -s ../Cellar/<name>/<version> $(brew --prefix)/opt/<name>`
  3. Re-run `mise bootstrap packages apply brew:<name>` to verify the record validates

Example fix

# before: opt/node is a plain directory
rm -rf "$(brew --prefix)/opt/node"
ln -s "$(brew --prefix)/Cellar/node/22.14.0" "$(brew --prefix)/opt/node"
Defensive patterns

Strategy: validation

Validate before calling

st=$(stat -c '%F' "$(brew --prefix)/opt/$name" 2>/dev/null) || true
[ "$st" = "symbolic link" ] || echo "opt/$name is $st, not a symlink — repair before applying"

Try / catch

match err.message() {
  m if m.contains("expected a symbolic link") => repair_opt_symlink(name),
  _ => return Err(err),
}

Prevention

When it happens

Trigger: Calling strict_package_root for a formula whose `$(brew --prefix)/opt/<name>` exists but is not a symlink — e.g. after a failed install left a directory there, a backup/restore turned the symlink into a real path, or something copied a file into opt/.

Common situations: Restoring the brew prefix from a tarball without symlink preservation (or on a filesystem that flattens symlinks); a crashed pour interrupted mid-link; manually experimenting in opt/.

Related errors


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

Appendix: source

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

    let prefix = prefix::prefix();
    let prefix = if prefix.is_absolute() {
        prefix
    } else {
        std::env::current_dir()
            .wrap_err_with(|| {
                format!(
                    "cannot resolve relative Homebrew prefix {}",
                    prefix.display()
                )
            })?
            .join(prefix)
    };
    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()
        );
    }

View on GitHub (pinned to 533346cc37)