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

  1. Reproduce manually: `ditto <from> <to>` and read the real diagnostic
  2. Free disk space and fix write permissions on the destination parent
  3. Ensure the destination volume supports extended attributes — avoid FAT/exFAT for caskrooms/appdirs
  4. 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

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


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/9331996c8a46f92c. Report an issue: GitHub.