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
- Remove the leftover staging directory (sudo rm if needed) at the printed location and retry the install
- Be consistent: always install the same cask either elevated or as the same user
- Check for a privileged daemon or script creating directories in the appdir
- 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
- Pick one elevation mode (sudo or not) per cask and stick to it
- Clean root-owned staging leftovers after interrupted elevated installs
- Don't run privileged daemons that create directories inside the appdir
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
- brew-cask: binary artifact
- brew-cask: binary artifact
- brew-cask: command wrapper
- brew-cask: completion artifact
- brew-cask: completion target
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)