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.

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.

Appendix: 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)