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

refusing to install update into non-app path: {}

Error message

refusing to install update into non-app path: {}

What it means

macOS-only guard in install_macos_app_update: the --target-app path passed to the updater does not end in .app, so copying the rebuilt bundle over it is refused. This prevents the updater from dittoing an application bundle on top of an arbitrary directory (e.g. /Applications itself or a user folder), which would corrupt the target.

Source

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

    while let Some(arg) = iter.next() {
        if arg == name {
            return iter.next();
        }
        if let Some(value) = arg.strip_prefix(&format!("{name}=")) {
            return Some(value.to_string());
        }
    }
    None
}

#[cfg(target_os = "macos")]
async fn install_macos_app_update(
    app: &AppHandle,
    install_root: &Path,
    target_app: &Path,
) -> Result<PathBuf> {
    if target_app.extension().and_then(|e| e.to_str()) != Some("app") {
        return Err(anyhow!(
            "refusing to install update into non-app path: {}",
            target_app.display()
        ));
    }

    let rebuilt_app = crate::bootstrap::resolve_hermes_desktop_app(install_root).ok_or_else(|| {
        anyhow!(
            "desktop rebuild succeeded but no Hermes.app was found under {}",
            install_root.join("apps").join("desktop").join("release").display()
        )
    })?;

    let same = match (rebuilt_app.canonicalize(), target_app.canonicalize()) {
        (Ok(a), Ok(b)) => a == b,
        _ => rebuilt_app == target_app,
    };
    if same {
        emit_log(

View on GitHub (pinned to c896c09c42)

Solutions

  1. Pass the full path to the .app bundle, e.g. --target-app /Applications/Hermes.app.
  2. Check the updater command line: only macOS parses target-app args, so verify argument order after any new flags.
  3. If you really want a non-bundle destination, that flow is unsupported — install the bundle and symlink instead.

Example fix

# before
hermes-setup --update --target-app /Applications

# after
hermes-setup --update --target-app /Applications/Hermes.app
Defensive patterns

Strategy: validation

Validate before calling

fn valid_target_app(p: &std::path::Path) -> bool {
    p.extension().and_then(|e| e.to_str()) == Some("app")
        && p.file_name().map(|n| n != "app").unwrap_or(false) // not a bare directory named 'app'
}

if !valid_target_app(&target_app) {
    eprintln!("--target-app must be a .app bundle path, got {}", target_app.display());
}

Type guard

fn is_app_bundle(p: &std::path::Path) -> bool {
    p.extension().and_then(|e| e.to_str()) == Some("app")
        && p.join("Contents").join("MacOS").exists()
}

Prevention

When it happens

Trigger: Invoking the updater with a --target-app argument that is a directory, .dmg, or path with a trailing component not named *.app; a wrapper script passing an install location instead of a bundle path; target_app_from_args parsing an unexpected flag as the path.

Common situations: A custom launchd/scripted update flow passes /Applications instead of /Applications/Hermes.app; a typo in the argument; argument order swapped so a branch name lands in the target-app position.

Related errors


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