NousResearch/hermes-agent · error · anyhow::Error

ditto failed while copying updated app into {}

Error message

ditto failed while copying updated app into {}

What it means

macOS-only failure: /usr/bin/ditto spawned successfully but exited non-zero while copying the rebuilt bundle into the staged <target>.hermes-update-new directory. Common ditto exit causes are source unreadable, destination volume issues, or metadata (code signing) errors during the copy.

Source

Thrown at apps/bootstrap-installer/src-tauri/src/update.rs:1048

    );

    if let Some(parent) = target_app.parent() {
        tokio::fs::create_dir_all(parent).await?;
    }
    let tmp = PathBuf::from(format!("{}.hermes-update-new", target_app.display()));
    let old = PathBuf::from(format!("{}.hermes-update-old", target_app.display()));
    remove_dir_if_exists(&tmp).await;
    remove_dir_if_exists(&old).await;

    let ditto = Command::new("/usr/bin/ditto")
        .arg(&rebuilt_app)
        .arg(&tmp)
        .current_dir(crate::paths::hermes_home())
        .status()
        .await
        .map_err(|e| anyhow!("running ditto: {e}"))?;
    if !ditto.success() {
        return Err(anyhow!(
            "ditto failed while copying updated app into {}",
            tmp.display()
        ));
    }

    // Atomic-as-possible swap with rollback. Extracted so the invariant
    // (target is never left deleted-with-no-replacement) can be unit-tested
    // without ditto / a real .app bundle.
    swap_in_new_bundle(&tmp, target_app, &old).await?;

    let _ = Command::new("/usr/bin/xattr")
        .arg("-dr")
        .arg("com.apple.quarantine")
        .arg(target_app)
        .current_dir(crate::paths::hermes_home())
        .status()
        .await;

View on GitHub (pinned to c896c09c42)

Solutions

  1. Free space on the target volume and retry the update.
  2. Verify the rebuilt bundle still exists under install_root/apps/desktop/release and opens manually.
  3. Reproduce manually: `/usr/bin/ditto <rebuilt.app> /tmp/test-copy.app` and read ditto's own error output.
  4. If signing/metadata is the issue, rebuild the desktop app cleanly and retry.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: source bundle readable, target volume has room for a second copy.
fn copy_precheck(src: &std::path::Path, dest_parent: &std::path::Path) -> std::io::Result<()> {
    let meta = std::fs::metadata(src)?;
    let need = meta.len() * 2; // staged copy + final
    let avail = fs2::available_space(dest_parent)?;
    if avail < need { return Err(std::io::Error::new(std::io::ErrorKind::StorageFull, "not enough space")); }
    Ok(())
}

Try / catch

if !ditto.success() {
    // tmp was fully staged or partially written; it is cleaned up by the caller's remove_dir_if_exists.
    return Err(anyhow!(
        "ditto failed (exit {:?}) while copying updated app into {}. Original app is untouched.",
        ditto.code(), tmp.display()
    ));
}

Prevention

When it happens

Trigger: The rebuilt .app under apps/desktop/release was moved/deleted between rebuild and copy; disk full on the target volume; copying across filesystems (staging on a different volume than /Applications) with metadata failures; source bundle with invalid signing data; target path's parent missing or read-only.

Common situations: Disk nearly full on the /Applications volume; the rebuild output got cleaned up by another process; a symlinked/nonstandard Applications dir; damaged code-signing post-build.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/d3b423dc39ed1ca7. Report an issue: GitHub.