jdx/mise · error
ditto failed copying {} to {}
Error message
ditto failed copying {} to {} What it means
ditto shells out to the macOS /usr/bin/ditto tool to copy bundles. This bail fires when ditto ran but exited with a non-zero status (a spawn failure surfaces earlier, wrapped as "failed to run ditto"). Both the source and destination paths are included so the exact copy can be reproduced by hand to see ditto's own diagnostic.
Source
Thrown at src/system/packages/brew/cask.rs:2641
.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)
}
}
fn ditto(from: &Path, to: &Path) -> Result<()> {
let status = std::process::Command::new("ditto")
.arg(from)
.arg(to)
.status()
.wrap_err("failed to run ditto")?;
if !status.success() {
bail!(
"ditto failed copying {} to {}",
from.display(),
to.display()
);
}
Ok(())
}
/// Run a helper with its working directory bound to `dir` via `fchdir`, so
/// relative arguments resolve from that exact directory inode.
///
/// Passing a pathname to a subprocess would let it re-resolve every component,
/// which a same-uid replacement can redirect. `fchdir` in the child pins
/// resolution to the descriptor mise already verified, so relative names cannot
/// escape the validated application directory.
#[cfg(unix)]
fn run_in_trusted_dir<Fd: std::os::fd::AsFd>(
program: &str,View on GitHub (pinned to 6f52dcdf99)
Solutions
- Reproduce manually: `ditto <from> <to>` and read the real diagnostic
- Free disk space and fix write permissions on the destination parent
- Ensure the destination volume supports extended attributes — avoid FAT/exFAT for caskrooms/appdirs
- On non-macOS, don't install casks whose artifacts require ditto bundle copies
Example fix
# before $ ditto /stage/Foo.app /Applications/Foo.app ditto: ... Permission denied # after $ sudo chown -R "$USER":admin /Applications/Foo.app 2>/dev/null; rm -rf /Applications/Foo.app $ ditto /stage/Foo.app /Applications/Foo.app
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight the copy the way ditto will do it.
if cfg!(target_os = "macos") {
std::process::Command::new("ditto").arg("--version").status()?; // ditto present
}
check_writable(destination.parent().unwrap())?;
check_free_space(destination, source_size)?; Try / catch
if let Err(err) = ditto(from, to) {
if err.to_string().starts_with("ditto failed copying") {
// run `ditto <from> <to>` by hand to surface the real diagnostic,
// fix perms/space, then retry the install once
}
} Prevention
- Keep the appdir/caskroom volumes off FAT/exFAT so xattr copies succeed
- Monitor disk space before large bundle installs
- Reproduce any ditto failure manually before changing configuration
When it happens
Trigger: ditto exits non-zero: permission denied reading the source or writing the destination, destination inside a read-only or SIP-protected location, disk full, source bundle missing mid-copy, or metadata (xattr/ACL) errors on the destination filesystem; also when ditto is absent (non-macOS) as the spawn-error variant.
Common situations: Full disk on the volume holding the destination; staging areas on filesystems that reject macOS metadata (FAT/exFAT); quarantined or unreadable source bundles; attempting bundle-copying casks on Linux.
Related errors
- ditto failed copying {} to {}
- brew-cask: staging directory is not owned by the current use
- brew-cask: failed to generate {} completions from {}: {}
- brew-cask:{}: pkg artifacts require pkgutil ids in uninstall
- `launchctl {}` failed: {}
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/9331996c8a46f92c.
Report an issue: GitHub.