jdx/mise · error

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

Error message

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

What it means

Before copying bundle contents into the destination staging directory with ditto, the library opens the directory and fstat()s it, verifying it is owned by the current effective uid. If the staging directory is owned by someone else (e.g. created by root or another user), proceeding could let an untrusted directory's contents be manipulated, so the operation is refused.

Source

Thrown at src/system/packages/brew/cask/mod.rs:2431

/// A racing creation of `name` surfaces as `EEXIST` and fails closed rather than
/// being followed.
#[cfg(unix)]
fn ditto_into<Fd: std::os::fd::AsFd>(from: &Path, dir: Fd, name: &std::ffi::OsStr) -> Result<()> {
    nix::sys::stat::mkdirat(&dir, name, nix::sys::stat::Mode::S_IRWXU).wrap_err_with(|| {
        format!(
            "brew-cask: cannot create staging directory {}",
            Path::new(name).display()
        )
    })?;
    let destination = open_dir_nofollow_at(&dir, name).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 afd2eddd3a)

Solutions

  1. Delete the root-owned staging directory (sudo rm -rf) and rerun the install as the normal user
  2. Stop running mise installs with sudo; chown -R "$USER" the mise cache/temp directories if needed
  3. In CI, ensure all steps run under the same user account

Example fix

// before
sudo mise install <cask>   # leaves root-owned staging dirs
// after
sudo rm -rf "$(mise cache dir)"/.../staging-*
chown -R "$USER" "$(mise cache dir)"
mise install <cask>
Defensive patterns

Strategy: validation

Validate before calling

use nix::sys::stat::fstat;
let stat = fstat(&dir)?;
if stat.st_uid != nix::unistd::geteuid().as_raw() {
    panic!("staging dir owned by uid {}; remove it and rerun as current user", stat.st_uid);
}

Type guard

fn staging_owned_by_me(dir: &impl nix::NixPath) -> bool {
    nix::sys::stat::fstat(dir)
        .map(|s| s.st_uid == nix::unistd::geteuid().as_raw())
        .unwrap_or(false)
}

Try / catch

match result {
    Err(e) if e.to_string().contains("not owned by the current user") => {
        eprintln!("delete the root-owned staging dir and reinstall without sudo");
    }
    Err(e) => return Err(e),
    Ok(v) => v,
}

Prevention

When it happens

Trigger: Opening the pre-created staging directory (by name under the mise temp/cask dir) succeeds but fstat shows st_uid != geteuid() — the directory exists but belongs to root or another account.

Common situations: A previous install was run with sudo/root, leaving root-owned staging dirs; running mise under a different user than the one that seeded the cache; CI switching between users.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/1a42fe7ac2a5e489. Report an issue: GitHub.