jdx/mise · error

ditto failed copying {} to {}

Error message

ditto failed copying {} to {}

What it means

On macOS, bundle-style cask artifacts are copied with the `ditto` command, which preserves metadata/resource forks. The library runs `ditto <from> <to>` and treats a non-zero exit status as a fatal copy failure, including both paths in the message for diagnosis.

Source

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

            .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 afd2eddd3a)

Solutions

  1. Re-run the install after checking `df -h` for free disk space
  2. Check permissions/ownership of the destination directory and the source bundle
  3. Re-download/re-extract the cask payload to rule out corruption
  4. Run the ditto command manually with the printed paths to see the underlying ditto error

Example fix

// diagnose manually
# ditto "$HOME/.cache/.../App.app" "$MISE_PREFIX/apps/App.app"; echo $?
// fix permissions then retry
chmod -R u+w "$MISE_PREFIX/apps" && mise install <cask>
Defensive patterns

Strategy: try-catch

Validate before calling

if Command::new("ditto").arg("--version").output().is_err() {
    panic!("ditto is unavailable; cask bundle copy will fail");
}
// also check disk space and writability of destination

Try / catch

match result {
    Err(e) if e.to_string().starts_with("ditto failed copying") => {
        eprintln!("check disk space, permissions, and run ditto manually with the printed paths");
    }
    Err(e) => return Err(e),
    Ok(v) => v,
}

Prevention

When it happens

Trigger: The spawned `ditto` process exits non-zero while copying a staged cask bundle (or contents into the destination staging dir) — source missing mid-copy, destination not writable, disk full, or ditto not functioning.

Common situations: Full APFS disk during a large app install; destination staging dir owned by another user or read-only; interrupted/removed source; corrupted download being extracted.

Related errors


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