{"record":{"id":"7c681569b16d53cb","repo":"NousResearch/hermes-agent","slug":"another-hermes-update-is-already-running-pid","errorCode":null,"errorMessage":"Another Hermes update is already running (PID {}, started {} ago). Wait for it to finish, or close the window or dashboard tab that started it, then try again.","messagePattern":"Another Hermes update is already running \\(PID (.+?), started (.+?) ago\\)\\. Wait for it to finish, or close the window or dashboard tab that started it, then try again\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"apps/bootstrap-installer/src-tauri/src/update.rs","lineNumber":294,"sourceCode":"            let elapsed = if mins > 0 {\n                format!(\"{mins}m {secs}s\")\n            } else {\n                format!(\"{secs}s\")\n            };\n            let msg = format!(\n                \"Another Hermes update is already running (PID {}, started {} ago). \\\n                 Wait for it to finish, or close the window or dashboard tab that \\\n                 started it, then try again.\",\n                owner.pid, elapsed\n            );\n            emit(\n                &app,\n                BootstrapEvent::Failed {\n                    stage: None,\n                    error: msg.clone(),\n                },\n            );\n            return Err(anyhow!(msg));\n        }\n    };\n\n    let update_branch = update_branch_from_args(std::env::args().skip(1))\n        .or_else(|| option_env_string(\"BUILD_PIN_BRANCH\"))\n        .unwrap_or_else(|| \"main\".to_string());\n    let target_app = if cfg!(target_os = \"macos\") {\n        target_app_from_args(std::env::args().skip(1))\n    } else {\n        None\n    };\n\n    let hermes = resolve_hermes(&install_root).ok_or_else(|| {\n        let msg = format!(\n            \"Could not find the hermes CLI under {}. Is Hermes installed? \\\n             Re-run the installer to repair the install.\",\n            install_root.display()\n        );","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/bootstrap-installer/src-tauri/src/update.rs#L276-L312","documentation":"Raised by the update flow's single-flight guard: the bootstrap completion marker doubles as a cross-process update lock (also claimed by hermes_cli/update_lock.py), and it currently names a live foreign owner PID with a start timestamp. The running update holds the lock; a second update attempt refuses to run concurrently and tells the user which PID holds it and for how long.","triggerScenarios":"Two dashboard tabs (or a dashboard tab plus a manually launched updater) triggering 'Check for updates' while the first update is still running; an update spawned by the desktop app that is stuck on a slow stage; a previous update whose owner process is genuinely still alive but hung.","commonSituations":"User opens the dashboard in two windows and clicks update in both; a cron/scripted update overlaps a manual one; an update stalled on a network stage long enough that the user retries instead of waiting.","solutions":["Wait for the in-flight update (the message includes its age) or close the window/tab that started it, then retry.","If the owner PID is a zombie from a crashed attempt, kill that PID (or fully exit Hermes) and retry.","Check the update log (~/.hermes/logs/update.log) to see whether the running update is progressing before killing it."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Check the lock owner before starting an update; mirror of the updater's probe.\nuse std::fs;\n\nfn update_in_progress(marker: &std::path::Path) -> Option<(u32, u64)> {\n    let body = fs::read_to_string(marker).ok()?;\n    let v: serde_json::Value = serde_json::from_str(&body).ok()?;\n    let pid = v.get(\"ownerPid\")?.as_u64()? as u32;\n    let started = v.get(\"ownerStartedUnix\")?.as_u64()?;\n    if pid != std::process::id() && pid_alive(pid) {\n        return Some((pid, started));\n    }\n    None\n}\n\n#[cfg(unix)]\nfn pid_alive(pid: u32) -> bool {\n    // kill(pid, 0): true if the process exists and we can signal it\n    unsafe { libc::kill(pid as i32, 0) == 0 }\n}","typeGuard":null,"tryCatchPattern":"// In UI code: disable the Update button instead of surfacing this as a crash.\nif let Some((pid, started)) = update_in_progress(&marker_path) {\n    set_update_button_disabled(format!(\"Update already running (PID {pid})\"));\n    return Ok(());\n}","preventionTips":["Have the UI probe the update lock and disable duplicate triggers instead of letting the second attempt error.","Ensure windows/tabs that start updates close cleanly so their updates finish and release the lock.","Never launch a second updater manually while the dashboard shows one in progress."],"tags":["concurrency","lock","update","bootstrap-installer"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}