NousResearch/hermes-agent · error · anyhow::Error
running ditto: {e}
Error message
running ditto: {e} What it means
macOS-only failure in the bundle-copy step: tokio could not even spawn `/usr/bin/ditto` to copy the rebuilt .app into the staged <target>.hermes-update-new path. This is a spawn/OS-level error (ditto missing, fork failure), distinct from ditto running and exiting non-zero, which is error 555.
Source
Thrown at apps/bootstrap-installer/src-tauri/src/update.rs:1046
target_app.display()
),
);
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()View on GitHub (pinned to c896c09c42)
Solutions
- Run `/usr/bin/ditto --help` in Terminal to confirm ditto exists and executes on that machine.
- Free system resources / raise ulimits and retry.
- If MDM/security software blocks it, exempt the updater or perform the update manually per the docs.
- Check the error text after the colon — it names the exact OS errno.
Defensive patterns
Strategy: validation
Validate before calling
fn ditto_available() -> bool {
std::path::Path::new("/usr/bin/ditto").exists()
&& std::process::Command::new("/usr/bin/ditto").arg("-h").output().is_ok()
}
if !ditto_available() {
eprintln!("/usr/bin/ditto unavailable — cannot stage the app update on this system.");
} Try / catch
match Command::new("/usr/bin/ditto").arg(&rebuilt_app).arg(&tmp).status().await {
Ok(st) if st.success() => { /* proceed to swap */ }
Ok(st) => Err(anyhow!("ditto failed while copying updated app into {}", tmp.display())),
Err(e) => Err(anyhow!("running ditto: {e} — is /usr/bin/ditto present and executable?")),
} Prevention
- On hardened/MDM Macs, confirm /usr/bin/ditto executes from your updater context before shipping.
- Watch system resource limits (fd/memory) when chaining many child processes.
When it happens
Trigger: /usr/bin/ditto absent on a gutted/locked-down macOS (rare, SIP-modified or non-standard system); process/resource limits hit (fork: resource temporarily unavailable); a security tool blocking execution of the child from the updater.
Common situations: Heavily hardened or MDM-locked Macs restricting child process execution; extremely low file descriptors/memory during the update; running the updater inside a restricted sandbox container where /usr/bin is not visible.
Related errors
- hermes update failed (exit {:?}). See {} for details.
- spawning {} {:?}: {e}
- waiting for child: {e}
- refusing to install update into non-app path: {}
- desktop rebuild succeeded but no Hermes.app was found under
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/99341048542be7be.
Report an issue: GitHub.