{"repo":"NousResearch/hermes-agent","public_id":"37557","display_label":"Desktop app self-update reliability across platforms","summary":"These issues and pull requests address bugs and improvements in the desktop app's in-app self-update flow on both macOS and Windows, covering problems such as silent failures, update loops, missing progress feedback, and unreliable updater handoff.","subsystem":"apps/desktop","counts":{"members":13,"open_issues":3,"open_issues_distinct":3,"open_prs":1,"closed_issues":6,"merged_prs":2,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":50448},"resolution":{"state":"partially_resolved","by":"pr-50448,pr-86675"},"freshness":{"state":"verified","last_verified_at":"2026-08-15T04:59:30+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.12410256410256412},"fix_edges":[{"issue_number":50419,"pr_number":50448,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":66753,"pr_number":86675,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":37557,"title":"[Bug]: Windows desktop: /update from chat silently hangs — no progress/logs, updater handoff not observable","url":null,"state":"closed"},{"kind":"issue","number":39248,"title":"[Bug]: Desktop app update process is broken","url":null,"state":"open"},{"kind":"issue","number":39339,"title":"[Bug]: Desktop app update installs but version remains unchanged — update loop on macOS","url":null,"state":"closed"},{"kind":"issue","number":43531,"title":"[Bug]: hermes desktop errors out when updating","url":null,"state":"closed"},{"kind":"issue","number":46076,"title":"Hermes Desktop in-app update does not reliably apply or relaunch on macOS","url":null,"state":"open"},{"kind":"issue","number":50419,"title":"feat(desktop/windows): show update progress before the desktop exits so it doesn't look like a crash","url":null,"state":"closed"},{"kind":"issue","number":57645,"title":"[Bug]: desktop in-app update on macOS closes without updating","url":null,"state":"closed"},{"kind":"issue","number":64577,"title":"[Bug]: macOS desktop self-update fails four ways on long-lived installs (stale staged updater, silent backup, canonical-app desync, retry-loop errors)","url":null,"state":"open"},{"kind":"issue","number":66753,"title":"[Bug]: Desktop exits without confirming detached updater spawn or early process survival","url":null,"state":"closed"},{"kind":"pr","number":42121,"title":"fix(desktop): stop macOS Update & Restart loop by always updating in-app","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":50448,"title":"feat(desktop/windows): show update-in-progress feedback before the desktop exits","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["apps/desktop/electron/main.cjs","apps/desktop/src/i18n/en.ts","apps/desktop/src/i18n/ja.ts","apps/desktop/src/i18n/zh-hant.ts","apps/desktop/src/i18n/zh.ts"],"fix_hunks":[{"file":"apps/desktop/electron/main.cjs","hunks":["@@ -1129,6 +1129,14 @@ function directoryExists(filePath) {\n // marker's own age ceiling; covers a stuck-but-alive updater).\n const UPDATE_WAIT_TIMEOUT_MS = 20 * 60 * 1000\n const UPDATE_WAIT_POLL_MS = 1000\n+// How long the desktop lingers on the \"updating, don't reopen\" overlay after\n+// spawning the detached updater, before it quits to release the venv shim. The\n+// old 600ms was long enough to register the child process but far too short for\n+// the user to READ the overlay — the window just vanished, looked like a crash,\n+// and the user relaunched mid-update (the #50238 restart-loop trigger). A\n+// couple of seconds lets the message land and bridges the gap until the\n+// updater's own progress window appears. (#50419)\n+const UPDATE_HANDOFF_DWELL_MS = 2500\n \n // Block until no live update is in progress (or we hit the wait timeout).\n // Emits a boot-progress phase so the renderer shows \"Update in progress…\"","@@ -1867,7 +1875,11 @@ async function applyUpdates(opts = {}) {\n       return { ok: true, manual: true, command, hermesRoot: updateRoot }\n     }\n \n-    emitUpdateProgress({ stage: 'restart', message: 'Handing off to the Hermes updater…', percent: 100 })\n+    emitUpdateProgress({\n+      stage: 'restart',\n+      message: 'Updating Hermes — this window will close and the updater will open. Don’t reopen Hermes yourself; it restarts automatically when the update finishes.',\n+      percent: 100\n+    })\n     repairMacUpdaterHelper(updater)\n \n     const updateRoot = resolveUpdateRoot()","@@ -1903,11 +1915,14 @@ async function applyUpdates(opts = {}) {\n \n     rememberLog(`[updates] launched updater: ${updater} ${updaterArgs.join(' ')}; exiting desktop to release venv shim`)\n \n-    // Give the OS a beat to register the new process, then quit. The updater\n-    // rebuilds and relaunches us when it's done.\n+    // Linger on the \"updating — don't reopen\" overlay long enough for the user\n+    // to actually read it (and to bridge the gap until the updater's own window\n+    // appears), THEN quit to release the venv shim. The updater rebuilds and\n+    // relaunches us when it's done. (#50419 — a 600ms quit looked like a crash\n+    // and lured users into the #50238 relaunch loop.)\n     setTimeout(() => {\n       app.quit()\n-    }, 600)\n+    }, UPDATE_HANDOFF_DWELL_MS)\n \n     return { ok: true, handedOff: true, updater }\n   } finally {","@@ -1946,9 +1961,12 @@ async function handOffWindowsBootstrapRecovery(reason) {\n   child.unref()\n \n   rememberLog(`[bootstrap] handed off ${reason} recovery to updater: ${updater} ${updaterArgs.join(' ')}; exiting desktop to release app.asar`)\n+  // Same dwell as the in-app update hand-off (#50419): give the updater's\n+  // window time to appear before we vanish, so the recovery doesn't look like\n+  // a crash and provoke a mid-recovery relaunch.\n   setTimeout(() => {\n     app.quit()\n-  }, 600)\n+  }, UPDATE_HANDOFF_DWELL_MS)\n \n   return true\n }"]},{"file":"apps/desktop/src/i18n/en.ts","hunks":["@@ -1382,10 +1382,11 @@ export const en: Translations = {\n     copy: 'Copy',\n     copied: 'Copied',\n     done: 'Done',\n-    applyingBody: 'The Hermes updater will take over in its own window and reopen Hermes when it’s done.',\n+    applyingBody:\n+      'The Hermes updater takes over in its own window and reopens Hermes automatically when it’s done. Please don’t reopen Hermes yourself while it’s updating.',\n     applyingBodyBackend:\n       'The remote backend is applying the update and will restart. Hermes reconnects automatically when it’s back.',\n-    applyingClose: 'Hermes will close to apply the update.',\n+    applyingClose: 'This window will close while the update runs, then Hermes reopens on its own.',\n     errorTitle: 'Update didn’t finish',\n     errorBody: 'No worries — nothing was lost. You can try again now.',\n     notNow: 'Not now',"]},{"file":"apps/desktop/src/i18n/ja.ts","hunks":["@@ -1512,9 +1512,9 @@ export const ja = defineLocale({\n     copy: 'コピー',\n     copied: 'コピーしました',\n     done: '完了',\n-    applyingBody: 'Hermes アップデーターが独自のウィンドウで引き継ぎ、完了後に Hermes を再度開きます。',\n+    applyingBody: 'Hermes アップデーターが独自のウィンドウで引き継ぎ、完了後に自動的に Hermes を再度開きます。更新中はご自分で Hermes を開き直さないでください。',\n     applyingBodyBackend: 'リモートバックエンドが更新を適用して再起動します。復帰すると Hermes が自動的に再接続します。',\n-    applyingClose: 'Hermes は更新を適用するために閉じます。',\n+    applyingClose: 'このウィンドウは更新中に閉じ、その後 Hermes が自動的に再度開きます。',\n     errorTitle: '更新が完了しませんでした',\n     errorBody: 'ご安心ください。何も失われていません。今すぐ再試行できます。',\n     notNow: '今は後で',"]},{"file":"apps/desktop/src/i18n/zh-hant.ts","hunks":["@@ -1463,9 +1463,9 @@ export const zhHant = defineLocale({\n     copy: '複製',\n     copied: '已複製',\n     done: '完成',\n-    applyingBody: 'Hermes 更新程式會在自己的視窗中接管，並在完成後重新開啟 Hermes。',\n+    applyingBody: 'Hermes 更新程式會在自己的視窗中接管，並在完成後自動重新開啟 Hermes。更新期間請勿自行重新開啟 Hermes。',\n     applyingBodyBackend: '遠端後端正在套用更新並將重新啟動。恢復後 Hermes 會自動重新連線。',\n-    applyingClose: 'Hermes 將關閉以套用更新。',\n+    applyingClose: '此視窗會在更新期間關閉，隨後 Hermes 會自動重新開啟。',\n     errorTitle: '更新未完成',\n     errorBody: '沒有資料遺失。您可以現在重試。',\n     notNow: '暫不',"]},{"file":"apps/desktop/src/i18n/zh.ts","hunks":["@@ -1568,9 +1568,9 @@ export const zh: Translations = {\n     copy: '复制',\n     copied: '已复制',\n     done: '完成',\n-    applyingBody: 'Hermes 更新器会在自己的窗口中接管，并在完成后重新打开 Hermes。',\n+    applyingBody: 'Hermes 更新器会在自己的窗口中接管，并在完成后自动重新打开 Hermes。更新期间请不要自行重新打开 Hermes。',\n     applyingBodyBackend: '远程后端正在应用更新并将重启。恢复后 Hermes 会自动重新连接。',\n-    applyingClose: 'Hermes 将关闭以应用更新。',\n+    applyingClose: '此窗口会在更新期间关闭，随后 Hermes 会自动重新打开。',\n     errorTitle: '更新未完成',\n     errorBody: '没有数据丢失。你可以现在重试。',\n     notNow: '暂不',\n"]}]},{"kind":"pr","number":57763,"title":"fix: macOS in-app update silent failure handling (#57645)","url":null,"state":"open","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":86675,"title":"fix(desktop): confirm detached updater spawn before quitting for hand-off (#66753)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["apps/desktop/electron/main.ts","apps/desktop/electron/updater-process.test.ts","apps/desktop/electron/updater-process.ts"]}],"trust":{"default_tier":"verified","includes_suggested":false}}