jdx/mise · error
brew-cask: temporary artifact directory was replaced
Error message
brew-cask: temporary artifact directory was replaced
What it means
Thrown while deleting the private staging directory mise creates inside the Caskroom to stage cask artifacts. The code holds an open directory descriptor and fstats it, then fstats the directory currently reachable at the staging name (AT_SYMLINK_NOFOLLOW). If the (st_dev, st_ino) pairs differ, the directory at that path was replaced after it was opened, so the unlinkat is refused instead of deleting whatever now lives at that name.
Source
Thrown at src/system/packages/brew/cask.rs:2047
nix::unistd::unlinkat(parent, name, nix::unistd::UnlinkatFlags::NoRemoveDir)?;
}
Ok(())
}
#[cfg(unix)]
fn remove_private_staging_dir(
parent: &TrustedOperationParent,
staging: &TrustedOperationParent,
staging_name: &std::ffi::OsStr,
) -> Result<()> {
let bound = nix::sys::stat::fstat(&staging.fd)?;
let linked = nix::sys::stat::fstatat(
&parent.fd,
staging_name,
nix::fcntl::AtFlags::AT_SYMLINK_NOFOLLOW,
)?;
if bound.st_dev != linked.st_dev || bound.st_ino != linked.st_ino {
bail!("brew-cask: temporary artifact directory was replaced");
}
nix::unistd::unlinkat(
&parent.fd,
staging_name,
nix::unistd::UnlinkatFlags::RemoveDir,
)?;
Ok(())
}
fn validate_generic_copy_target(target: &Path) -> Result<()> {
let prefix = prefix::prefix();
if !target.starts_with(&prefix)
|| target.strip_prefix(&prefix)?.components().next().is_none()
|| !path_starts_with_resolved_root(target, &prefix)
{
bail!(
"brew-cask: refusing generic artifact copy outside Homebrew prefix: {}",
target.display()View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Make sure no other mise/brew cask process is running against the same Caskroom, then retry the command
- Inspect the staging parent under the Caskroom, delete the leftover/replaced staging directory manually, and re-run
- Disable third-party cleanup or antivirus tools that touch the Caskroom during installs
- If it reproduces with nothing else running, capture the staging path and report it: it can indicate a local security issue or an exotic filesystem
Defensive patterns
Strategy: retry
Validate before calling
use std::os::unix::io::AsRawFd;
use nix::fcntl::{flock, FlockArg};
// Serialize cask operations per Caskroom so no second process can swap staging dirs
fn lock_caskroom(caskroom: &std::path::Path) -> std::fs::File {
let lock = std::fs::OpenOptions::new()
.create(true).append(true)
.open(caskroom.join(".mise-cask.lock")).expect("open lock");
flock(lock.as_raw_fd(), FlockArg::LockExclusive).expect("lock caskroom");
lock // keep alive for the whole install/uninstall
} Try / catch
match run_cask_op(&cask) {
Err(e) if e.to_string().contains("temporary artifact directory was replaced") => {
// transient race: wait for the concurrent operation, then retry once
std::thread::sleep(std::time::Duration::from_secs(2));
run_cask_op(&cask)?
}
other => other?,
} Prevention
- Never run two mise/brew cask operations against the same Caskroom at once
- Keep the Caskroom on a local filesystem (inode identity is unreliable on NFS/SMB)
- Exclude the Caskroom from temp-cleaning and antivirus tools
When it happens
Trigger: A cask install/uninstall run in which another process removes and recreates the staging directory (or swaps a symlink in its place) between staging-dir creation and cleanup: a second mise/brew cask process on the same Caskroom, a temp-file cleaner, or a deliberate symlink-swap race this check exists to catch.
Common situations: Two concurrent cask operations in one Caskroom; cleanup/'optimizer' tools purging staging trees mid-install; a previously crashed run leaving a different directory at the staging name; Caskroom on a network filesystem where inode identity is unstable.
Related errors
- brew-cask: refusing elevated operation because target appear
- brew-cask: refusing operation through a changed generic arti
- install from exe
- mise outdated --monorepo is not implemented yet
- mise prune --monorepo is not implemented yet
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/d19801dff1899b4c.
Report an issue: GitHub.