jdx/mise · error
brew-cask: generic artifact backup changed directories
Error message
brew-cask: generic artifact backup changed directories
What it means
The backup/restore rename of a generic artifact is performed with renameat on a single opened parent directory descriptor, which only works when source and destination share one directory. rename_trusted_generic_target therefore requires from.parent() == to.parent() and refuses when the backup path lives in a different directory.
Source
Thrown at src/system/packages/brew/cask.rs:2488
fn validate_trusted_operation_parent(
parent: &TrustedOperationParent,
expected_parent: &Path,
) -> Result<()> {
let actual_parent = std::fs::canonicalize(parent.path()?)?;
if actual_parent != expected_parent {
bail!(
"brew-cask: refusing operation through a changed generic artifact parent: {}",
expected_parent.display()
);
}
Ok(())
}
fn rename_trusted_generic_target(from: &Path, to: &Path, expected_parent: &Path) -> Result<()> {
#[cfg(unix)]
{
if from.parent() != to.parent() {
bail!("brew-cask: generic artifact backup changed directories");
}
let parent = open_trusted_operation_parent(from, true, false)?;
validate_trusted_operation_parent(&parent, expected_parent)?;
let from_name = from
.file_name()
.ok_or_else(|| eyre!("brew-cask: generic artifact source has no filename"))?;
let to_name = to
.file_name()
.ok_or_else(|| eyre!("brew-cask: generic artifact target has no filename"))?;
nix::fcntl::renameat(&parent.fd, from_name, &parent.fd, to_name)?;
Ok(())
}
#[cfg(not(unix))]
{
let _ = expected_parent;
file::rename(from, to)
}
}View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Build the backup path in the same directory as the target: target.with_file_name(format!(".{}.mise-old", name))
- If calling rename_trusted_generic_target directly, assert from.parent() == to.parent() beforehand
- Keep backup/restore inside one directory so the single-parent rename stays valid
Example fix
// before -- backup created in another directory
let backup = std::env::temp_dir().join(target.file_name().unwrap());
// after -- same parent as the target
let backup = target.with_file_name(format!(".{}.mise-old", target.file_name().unwrap().to_string_lossy())); Defensive patterns
Strategy: validation
Validate before calling
if from.parent() != to.parent() {
anyhow::bail!("backup path must share the target's directory: {} vs {}",
from.display(), to.display());
} Prevention
- Always derive backup paths with target.with_file_name(...) so they stay in the same directory
- Never relocate backups to a temp dir before the rename completes
- Assert from.parent() == to.parent() before calling rename helpers
When it happens
Trigger: A caller constructing the backup path under a different directory (e.g. a temp dir) than the live target; any code path where the '.mise-old' backup name is appended to a different base path.
Common situations: Custom wrappers moving backups to /tmp; orchestration bugs mixing up source and target parents; almost never caused by end-user configuration.
Related errors
- 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
- mise upgrade --monorepo is not implemented yet
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/bc6ff41f56672156.
Report an issue: GitHub.