NousResearch/hermes-agent · warning
An update is already in progress.
Error message
An update is already in progress.
What it means
A re-entrancy guard in applyUpdates: the module-level updateInFlight flag is already true, meaning an update handoff is in progress. The desktop never updates itself; it spawns the Hermes-Setup binary with --update and quits, and this flag prevents a second concurrent trigger (double-click, queued IPC) from starting a second updater.
Source
Thrown at apps/desktop/electron/main.ts:2894
`[${tag}] venv shim still locked after 15s; aborting hand-off (something outside this app holds the venv)`
)
return { unlocked: false }
}
// applyUpdates — hand off to the installer's --update flow, then exit.
//
// The desktop is a pure consumer: it does NOT git pull / pip install / rebuild
// itself (the old open-coded git dance lived here and drifted from
// `hermes update`). Instead we spawn the staged Hermes-Setup binary with
// --update and quit, so it can run `hermes update` (which refuses while we
// hold the venv shim) and rebuild the desktop with our exe already gone.
//
// Detection (checkUpdates / commit changelog / "N behind") stays in the UI;
// only this apply action changed.
async function applyUpdates(opts = {}) {
if (updateInFlight) {
throw new Error('An update is already in progress.')
}
updateInFlight = true
try {
const updater = resolveUpdaterBinary()
if (!updater && !IS_WINDOWS) {
// macOS/Linux: hand off to the repo-owned posix script — same shape as
// Windows (quit → detached orchestrator → `hermes update` → relaunch),
// minus the venv-lock gauntlet POSIX doesn't need. The old in-app
// updater (applyUpdatesPosixInApp) is gone with everything it dragged
// in: the HERMES_DESKTOP_CHILD_PID reaper-exclusion dance (#37532),
// the in-window rebuild retry, and the relaunch-outcome matrix — the
// script owns swap/relaunch, and the app is DEAD during the update so
// there is nothing to reap around. Checkouts that predate the script
// get the manual `hermes update` card once; their next update pulls it.
return await applyUpdatesPosixHandoff(opts)View on GitHub (pinned to c896c09c42)
Solutions
- Wait for the in-flight update to finish — the app will quit and relaunch via the installer flow.
- If the app seems stuck (no quit after a long wait), restart the desktop app and trigger the update once.
- In renderer code, disable the update button as soon as the first applyUpdates call begins.
Example fix
// before
updateButton.onclick = () => void ipc.invoke('updates:apply')
// after — disable on first click to avoid the guard firing
updateButton.onclick = () => {
updateButton.disabled = true
ipc.invoke('updates:apply').finally(() => (updateButton.disabled = false))
} Defensive patterns
Strategy: type-guard
Type guard
// Guard at the renderer: only one apply at a time
let applyingUpdate = false
async function safeApplyUpdates() {
if (applyingUpdate) return // update already in progress
applyingUpdate = true
try { await applyUpdates() } finally { applyingUpdate = false }
} Try / catch
try {
await applyUpdates()
} catch (e) {
if (/already in progress/.test(e.message)) return // benign duplicate trigger
throw e
} Prevention
- Disable the update button on first click
- Treat 'already in progress' as a no-op, not an error
- Debounce rapid IPC triggers in the renderer
When it happens
Trigger: Invoking the apply-update IPC twice in quick succession (double-click on the update button, or the renderer firing the action again before the app quits); an update handoff that started but the quit is slow.
Common situations: Impatient double-clicks on 'Restart & Update'; race between an auto-triggered update and a manual one.
Related errors
- Secure token storage is unavailable (no OS keyring service w
- Failed to encrypt the remote gateway token for secure storag
- Hermes desktop bridge unavailable
- Hermes Desktop bridge is unavailable
- Saving is not available
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/6c26949396d1fc4b.
Report an issue: GitHub.