jdx/mise · error

ditto failed copying

Error message

ditto failed copying {} to {}

What it means

mise copies cask bundle contents into its private staging directory by invoking the macOS 'ditto' tool in a trusted directory context. When ditto exits with a non-zero status, mise wraps the failure in this error reporting the source path and the staging destination. Causes include disk-full, permission problems on the source bundle, or source files disappearing mid-copy.

Solutions

  1. Re-run the cask install; a transient source/IO failure often resolves on retry after the artifact is re-downloaded.
  2. Free disk space on the destination volume and check it is not read-only.
  3. Verify the source bundle path exists and is fully downloaded (clear the cache and re-download the cask).
  4. Check permissions/xattrs on the source files (dequarantine with xattr -dr com.apple.quarantine <path> if needed).
  5. Ensure 'ditto' is present and functional (it ships with macOS; a stripped-down environment may lack it).
Defensive patterns

Strategy: retry

Validate before calling

let src = std::path::Path::new(&from);
if !src.exists() {
    eprintln!("source bundle missing before copy: {}", src.display());
    std::process::exit(1);
}
let free = fs_free_bytes(&dest_volume);
if free < estimated_size {
    eprintln!("insufficient disk space: {free} bytes free");
    std::process::exit(1);
}

Try / catch

match result {
    Err(e) if e.to_string().contains("ditto failed copying") => {
        // clear cache, free disk space, then retry install once
        clear_brew_cache()?;
        retry_install()?;
    }
    r => r?,
}

Prevention

When it happens

Trigger: run_in_trusted_dir("ditto", [from, "."], destination) returns a failing exit status; the source bundle was deleted or renamed during install; target volume is full or read-only; source contains unreadable/locked files.

Common situations: Full APFS volume during large cask installs; downloads folder cleanup racing the install; iCloud-offloaded files that cannot be materialized; quarantine/xattr issues making source files unreadable; running out of disk in ~/Library/Caches.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/b23449dfa031b50d. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/cask/mod.rs:2658

    let destination = open_dir_nofollow_at(&dir, name).wrap_err_with(|| {
        format!(
            "brew-cask: cannot open staging directory {}",
            Path::new(name).display()
        )
    })?;
    let stat = nix::sys::stat::fstat(&destination)?;
    if stat.st_uid != nix::unistd::geteuid().as_raw() {
        bail!("brew-cask: staging directory is not owned by the current user");
    }
    // `ditto src dst` copies the *contents* of src into dst, so pointing it at
    // the bound directory reproduces the bundle in place.
    let status = run_in_trusted_dir(
        "ditto",
        &[from.as_os_str(), std::ffi::OsStr::new(".")],
        &destination,
    )?;
    if !status.success() {
        bail!(
            "ditto failed copying {} to {}",
            from.display(),
            Path::new(name).display()
        );
    }
    // Restore the bundle's own permissions, which the private staging mode hid.
    if let Ok(metadata) = from.symlink_metadata() {
        use std::os::unix::fs::PermissionsExt;
        let mode = nix::sys::stat::Mode::from_bits_truncate(
            metadata.permissions().mode() as nix::libc::mode_t
        );
        nix::sys::stat::fchmod(&destination, mode)?;
    }
    Ok(())
}

fn install_pkg(stage: &Path, pkg: &PkgArtifact) -> Result<()> {
    let source = find_file_artifact(stage, &pkg.source)

View on GitHub (pinned to 533346cc37)