jdx/mise · error
brew-cask: refusing elevated operation because target appear
Error message
brew-cask: refusing elevated operation because target appeared: {} What it means
This error is a TOCTOU (time-of-check-time-of-use) safety guard in `ensure_target_absent`. Before performing an elevated operation against a path, the function verifies the path does not exist via `symlink_metadata`; if a file, directory, or symlink has appeared at the target between the initial check and the elevated action, the operation is aborted rather than clobbering an object an attacker may have planted. It protects against symlink-swap races where a malicious local process creates a link at the destination path mid-operation.
Source
Thrown at src/system/packages/brew/cask/mod.rs:1758
fn strict_elevated_directory_is_trusted(
directory: &Path,
stable_prefix: &Path,
uid: u32,
mode: u32,
) -> bool {
uid == 0
&& mode & 0o002 == 0
// Intel Homebrew conventionally uses root:admin 0775 for /usr/local.
// Permit that exact prefix, but require every descendant and every
// other ancestor used by the elevated operation to be non-writable.
&& (mode & 0o020 == 0 || directory == stable_prefix)
}
#[cfg(unix)]
fn ensure_target_absent(target: &Path) -> Result<()> {
match target.symlink_metadata() {
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
Ok(_) => bail!(
"brew-cask: refusing elevated operation because target appeared: {}",
target.display()
),
Err(err) => Err(err.into()),
}
}
/// Opens a directory relative to `parent`, never following symlinks.
#[cfg(unix)]
fn open_dir_nofollow_at<Fd: std::os::fd::AsFd, P: nix::NixPath + ?Sized>(
parent: Fd,
name: &P,
) -> Result<std::os::fd::OwnedFd> {
Ok(nix::fcntl::openat(
parent,
name,
nix::fcntl::OFlag::O_RDONLY
| nix::fcntl::OFlag::O_DIRECTORYView on GitHub (pinned to afd2eddd3a)
Solutions
- Inspect the path named in the message (`target.display()`); if it is a leftover from an earlier failed run and nothing depends on it, remove the file/symlink and retry.
- Verify no other brew or cask process is running concurrently against the same prefix; serialize operations.
- If the target is legitimately expected to exist, this operation is intentionally refused — restructure the call so the path is clear, or use a non-elevated path that handles existing targets.
- If the path reappears on every attempt, check for background jobs (launchd/cron, other package managers) recreating it.
Defensive patterns
Strategy: validation
Validate before calling
use std::path::Path;
fn target_is_clear(target: &Path) -> bool {
matches!(target.symlink_metadata(), Err(e) if e.kind() == std::io::ErrorKind::NotFound)
}
// before calling the elevated operation:
if !target_is_clear(&target) { eprintln!("target exists: {}", target.display()); }
Type guard
fn path_is_absent(p: &Path) -> bool {
p.symlink_metadata().map_err(|e| e.kind() == std::io::ErrorKind::NotFound).unwrap_or(false)
}
Prevention
- Clean up leftover files/symlinks at artifact destinations after failed installs before retrying.
- Never run concurrent brew/cask operations against the same prefix.
- Ensure no background jobs write into the Homebrew prefix.
When it happens
Trigger: Calling a privileged brew-cask operation that resolves through `ensure_target_absent` when `target.symlink_metadata()` returns `Ok` — i.e. the target path (or a symlink at that path) exists at the moment of the re-check, typically because another process created it concurrently or a stale file from a previous failed run is present.
Common situations: A previous interrupted install left a file or dangling symlink at the cask artifact destination; a parallel brew/cask process racing on the same path; a malicious or accidental symlink planted at the destination; Nix/tmpfs state carried over between runs.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- brew-cask: refusing operation through a changed generic arti
- expected pre-planted symlink destination to be refused
- created path component {} was replaced before it could be op
- brew-cask: temporary artifact directory was replaced
- brew-cask: staged symlink path escaped extraction root: {}
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/69fd819feea68b8d.
Report an issue: GitHub.