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
- Free space on the target volume and retry the update.
- Verify the rebuilt bundle still exists under install_root/apps/desktop/release and opens manually.
- Reproduce manually: `/usr/bin/ditto <rebuilt.app> /tmp/test-copy.app` and read ditto's own error output.
- 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
- Ensure at least 2x the app-bundle size free on the target volume before updating.
- Verify the rebuilt bundle exists and is stable (no concurrent cleanup) before the copy step.
- Keep HERMES_HOME staging and the target .app on the same volume.
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
- desktop rebuild succeeded but no Hermes.app was found under
- could not move existing app aside at {} (leaving it in place
- installing updated app at {}: {err}
- Could not find the hermes CLI under {}. Is Hermes installed?
- refusing to install update into non-app path: {}
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/d3b423dc39ed1ca7.
Report an issue: GitHub.