jdx/mise · error

brew-cask: staging directory is not owned by the current…

Error message

brew-cask: staging directory is not owned by the current user

What it means

ditto_into creates a private staging directory with mkdirat inside a verified parent, reopens it with openat/O_NOFOLLOW, and then requires fstat st_uid of the opened directory to equal the effective uid. If the directory at that name was created or replaced by a different (e.g. root/elevated) process, ownership differs and mise refuses to run ditto into it — the copy would land in a directory controlled by another uid.

Solutions

  1. Remove the leftover staging directory (sudo rm if needed) at the printed location and retry the install
  2. Be consistent: always install the same cask either elevated or as the same user
  3. Check for a privileged daemon or script creating directories in the appdir
  4. Re-run once the mismatched directory is gone

Example fix

# before
$ mise brew install --cask foo   # staging dir left root-owned by earlier sudo run

# after
$ sudo rm -rf "/path/from/error/message" && mise brew install --cask foo
Defensive patterns

Strategy: validation

Validate before calling

// Before install, ensure no foreign-owned staging leftovers exist.
use std::os::unix::fs::MetadataExt;
for entry in std::fs::read_dir(appdir)? {
    let entry = entry?;
    let md = entry.metadata()?;
    if md.uid() != nix::unistd::geteuid().as_raw() {
        return Err(eyre!("foreign-owned entry {} in appdir", entry.path().display()));
    }
}

Type guard

fn owned_by_current_user(md: &std::fs::Metadata) -> bool {
    use std::os::unix::fs::MetadataExt;
    md.uid() == nix::unistd::geteuid().as_raw()
}

Prevention

When it happens

Trigger: An elevated helper or an earlier sudo run left or created the staging directory as root while the current run is unprivileged (or the reverse); a racing creation of the predictable name by a different uid between mkdirat and openat.

Common situations: Alternating between sudo and non-sudo installs of the same cask; leftover root-owned staging dirs from an interrupted elevated install; environments where euid changes mid-run (containers, CI service users).

Related errors


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/3ea2a1b5e52a7d9d. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/cask.rs:2721

        )
    })?;
    let destination = nix::fcntl::openat(
        &dir,
        name,
        nix::fcntl::OFlag::O_RDONLY
            | nix::fcntl::OFlag::O_DIRECTORY
            | nix::fcntl::OFlag::O_NOFOLLOW,
        nix::sys::stat::Mode::empty(),
    )
    .wrap_err_with(|| {
        format!(
            "brew-cask: cannot open staging directory {}",
            Path::new(name).display()
        )
    })?;
    let stat = nix::sys::stat::fstat(&destination)?;
    if stat.st_uid != nix::unistd::geteuid().as_raw() {
        bail!("brew-cask: staging directory is not owned by the current user");
    }
    // `ditto src dst` copies the *contents* of src into dst, so pointing it at
    // the bound directory reproduces the bundle in place.
    let status = run_in_trusted_dir(
        "ditto",
        &[from.as_os_str(), std::ffi::OsStr::new(".")],
        &destination,
    )?;
    if !status.success() {
        bail!(
            "ditto failed copying {} to {}",
            from.display(),
            Path::new(name).display()
        );
    }
    // Restore the bundle's own permissions, which the private staging mode hid.
    if let Ok(metadata) = from.symlink_metadata() {
        use std::os::unix::fs::PermissionsExt;

View on GitHub (pinned to 6f52dcdf99)