{"record":{"id":"3ea2a1b5e52a7d9d","repo":"jdx/mise","slug":"brew-cask-staging-directory-is-not-owned-by-the-c","errorCode":null,"errorMessage":"brew-cask: staging directory is not owned by the current user","messagePattern":"brew-cask: staging directory is not owned by the current user","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":2721,"sourceCode":"        )\n    })?;\n    let destination = nix::fcntl::openat(\n        &dir,\n        name,\n        nix::fcntl::OFlag::O_RDONLY\n            | nix::fcntl::OFlag::O_DIRECTORY\n            | nix::fcntl::OFlag::O_NOFOLLOW,\n        nix::sys::stat::Mode::empty(),\n    )\n    .wrap_err_with(|| {\n        format!(\n            \"brew-cask: cannot open staging directory {}\",\n            Path::new(name).display()\n        )\n    })?;\n    let stat = nix::sys::stat::fstat(&destination)?;\n    if stat.st_uid != nix::unistd::geteuid().as_raw() {\n        bail!(\"brew-cask: staging directory is not owned by the current user\");\n    }\n    // `ditto src dst` copies the *contents* of src into dst, so pointing it at\n    // the bound directory reproduces the bundle in place.\n    let status = run_in_trusted_dir(\n        \"ditto\",\n        &[from.as_os_str(), std::ffi::OsStr::new(\".\")],\n        &destination,\n    )?;\n    if !status.success() {\n        bail!(\n            \"ditto failed copying {} to {}\",\n            from.display(),\n            Path::new(name).display()\n        );\n    }\n    // Restore the bundle's own permissions, which the private staging mode hid.\n    if let Ok(metadata) = from.symlink_metadata() {\n        use std::os::unix::fs::PermissionsExt;","sourceCodeStart":2703,"sourceCodeEnd":2739,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L2703-L2739","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"# before\n$ mise brew install --cask foo   # staging dir left root-owned by earlier sudo run\n\n# after\n$ sudo rm -rf \"/path/from/error/message\" && mise brew install --cask foo","handlingStrategy":"validation","validationCode":"// Before install, ensure no foreign-owned staging leftovers exist.\nuse std::os::unix::fs::MetadataExt;\nfor entry in std::fs::read_dir(appdir)? {\n    let entry = entry?;\n    let md = entry.metadata()?;\n    if md.uid() != nix::unistd::geteuid().as_raw() {\n        return Err(eyre!(\"foreign-owned entry {} in appdir\", entry.path().display()));\n    }\n}","typeGuard":"fn owned_by_current_user(md: &std::fs::Metadata) -> bool {\n    use std::os::unix::fs::MetadataExt;\n    md.uid() == nix::unistd::geteuid().as_raw()\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["brew-cask","ownership","staging","sudo","ditto"],"backgroundTag":"file-ownership-mismatch","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}