NousResearch/hermes-agent · error · anyhow::Error
could not move existing app aside at {} (leaving it in place
Error message
could not move existing app aside at {} (leaving it in place): {err} What it means
First phase of the atomic bundle swap in swap_in_new_bundle: renaming the existing target .app to <target>.hermes-update-old failed, so the updater removes the staged tmp copy and leaves the original app untouched. The invariant is that a failed update must never brick the install — bail before touching the original rather than risk deleting the running app with no replacement.
Source
Thrown at apps/bootstrap-installer/src-tauri/src/update.rs:1084
Ok(target_app.to_path_buf())
}
/// Move a freshly-staged bundle (`tmp`) into place at `target`, parking any
/// existing bundle at `old` so the move can succeed (macOS `rename` won't
/// overwrite a non-empty directory).
///
/// Invariant: on ANY failure path, `target` is left pointing at a working
/// bundle — either the original (rolled back from `old`) or untouched — and we
/// never delete the running app with no replacement in place. The staged `tmp`
/// copy is cleaned up on failure.
async fn swap_in_new_bundle(tmp: &Path, target: &Path, old: &Path) -> Result<()> {
let moved_old = if target.exists() {
if let Err(err) = tokio::fs::rename(target, old).await {
// Could not move the existing app aside. Leave it untouched and
// bail — a failed update must not brick the install.
remove_dir_if_exists(tmp).await;
return Err(anyhow!(
"could not move existing app aside at {} (leaving it in place): {err}",
target.display()
));
}
true
} else {
false
};
if let Err(err) = tokio::fs::rename(tmp, target).await {
// Restore the original app from the backup so the user keeps a working
// install, and clean up the staged copy.
if moved_old {
let _ = tokio::fs::rename(old, target).await;
}
remove_dir_if_exists(tmp).await;
return Err(anyhow!("installing updated app at {}: {err}", target.display()));
}
remove_dir_if_exists(old).await;View on GitHub (pinned to c896c09c42)
Solutions
- Quit Hermes completely (check Activity Monitor) and retry the update.
- Remove leftover <app>.hermes-update-old / -new siblings from the previous failed attempt.
- Fix permissions on the target's parent directory (chown / reinstall into user-writable location).
- If /Applications requires admin, run the updater with the necessary privileges or move the app to ~/Applications.
Defensive patterns
Strategy: validation
Validate before calling
// Pre-swap checks: old app not running, no stale leftovers, writable parent.
fn swap_precheck(target: &std::path::Path) -> Result<(), String> {
let old = std::path::PathBuf::from(format!("{}.hermes-update-old", target.display()));
let tmp = std::path::PathBuf::from(format!("{}.hermes-update-new", target.display()));
if old.exists() { return Err(format!("stale leftover {} from a failed update — remove it first", old.display())); }
if tmp.exists() { return Err(format!("stale leftover {} — remove it first", tmp.display())); }
if target.parent().map(|p| p.metadata().is_ok_and(|m| m.permissions().readonly())).unwrap_or(true) {
return Err("target parent directory is read-only".into());
}
Ok(())
} Try / catch
// The function already implements the correct pattern: on failed move-aside, delete tmp and
// leave the original untouched — never continue into a state where the target is deleted.
match tokio::fs::rename(target, old).await {
Ok(()) => { /* proceed to rename tmp -> target */ }
Err(err) => {
remove_dir_if_exists(tmp).await; // staged copy cleaned
return Err(anyhow!("could not move existing app aside at {} (leaving it in place): {err}", target.display()));
}
} Prevention
- Quit the app fully before updating — a running bundle cannot be renamed on macOS.
- Clear .hermes-update-old/.hermes-update-new leftovers after any failed update.
- Install the app in a user-writable location (~/Applications) if /Applications needs elevation.
When it happens
Trigger: The target .app is busy — macOS refuses to rename a bundle that is running or has files held open; permission denied on /Applications (app installed without user-writable perms); the .hermes-update-old leftover from a prior crashed update already exists as a file where a directory rename is required.
Common situations: The old Hermes.app is still running when the swap starts (stage-1 race); user-installed into /Applications under an admin context so the rename needs elevation; leftover stale .hermes-update-old path from an earlier failed run.
Related errors
- installing updated app at {}: {err}
- desktop rebuild succeeded but no Hermes.app was found under
- ditto failed while copying updated app into {}
- 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/c280dd3f20755672.
Report an issue: GitHub.