{"repo":"NousResearch/hermes-agent","public_id":"56884","display_label":"Hide console window during Windows desktop updater handoff","summary":"These issues and PRs relate to a console window appearing during the Windows desktop application update process. The work involves suppressing or hiding that console window during the updater handoff step.","subsystem":"apps/desktop","counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":66040,"merged_at":"2026-07-17T02:50:41+00:00"},"resolution":{"state":"resolved","by":"pr-66040"},"freshness":{"state":"verified","last_verified_at":"2026-07-17T02:50:41+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":56884,"pr_number":66040,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-07-17T02:50:41+00:00"}],"members":[{"kind":"issue","number":56884,"title":"hermes-setup.exe shows console window during Desktop update","url":null,"state":"closed"},{"kind":"pr","number":57457,"title":"fix(desktop): hide Windows updater console during hand-off (#56884)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#56907","number":56907},{"kind":"pr","value":"#56884","number":56884}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":66040,"title":"fix(desktop): hide Windows updater console during handoff","url":null,"state":"merged","close_reason":{"category":"merged"},"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"],"fix_hunks":[{"file":"apps/desktop/electron/main.ts","hunks":["@@ -126,6 +126,7 @@ import {\n   sandboxPreflight\n } from './update-relaunch'\n import { isOfficialSshRemote, OFFICIAL_REPO_HTTPS_URL } from './update-remote'\n+import { spawnUpdaterProcess } from './updater-process'\n import { fetchMarketplaceThemes, searchMarketplaceThemes } from './vscode-marketplace'\n import {\n   computeWindowOptions,","@@ -2520,7 +2521,7 @@ async function applyUpdates(opts = {}) {\n \n     // Detached so the updater outlives this process — it needs us GONE before\n     // `hermes update` will run (the venv shim is locked while we live).\n-    const child = spawn(updater, updaterArgs, {\n+    const child = spawnUpdaterProcess(updater, updaterArgs, {\n       cwd: HERMES_HOME,\n       env: {\n         ...process.env,","@@ -2528,12 +2529,9 @@ async function applyUpdates(opts = {}) {\n         PATH: pathWithHermesManagedNode(venvBin)\n       },\n       detached: true,\n-      stdio: 'ignore',\n-      windowsHide: false\n+      stdio: 'ignore'\n     })\n \n-    child.unref()\n-\n     // Write the update-in-progress marker IMMEDIATELY — before the 2.5s\n     // quit dwell. The Tauri updater won't write its own marker for several\n     // seconds (window init + manifest), and during that gap our renderer","@@ -2598,7 +2596,7 @@ async function handOffWindowsBootstrapRecovery(reason) {\n \n   await releaseBackendLockForUpdate(updateRoot)\n \n-  const child = spawn(updater, updaterArgs, {\n+  const child = spawnUpdaterProcess(updater, updaterArgs, {\n     cwd: HERMES_HOME,\n     env: {\n       ...process.env,","@@ -2606,12 +2604,9 @@ async function handOffWindowsBootstrapRecovery(reason) {\n       PATH: pathWithHermesManagedNode(venvBin)\n     },\n     detached: true,\n-    stdio: 'ignore',\n-    windowsHide: false\n+    stdio: 'ignore'\n   })\n \n-  child.unref()\n-\n   // Same marker pre-write as applyUpdates — see comment there. The recovery\n   // hand-off has the same window where the renderer can respawn a backend\n   // before the updater writes its own marker."]},{"file":"apps/desktop/electron/updater-process.test.ts","hunks":["@@ -0,0 +1,62 @@\n+import assert from 'node:assert/strict'\n+import type { SpawnOptions } from 'node:child_process'\n+\n+import { test } from 'vitest'\n+\n+import { spawnUpdaterProcess } from './updater-process'\n+\n+test('spawnUpdaterProcess hides the updater console and detaches the child on Windows', () => {\n+  const calls: Array<{ args: string[]; command: string; options: SpawnOptions }> = []\n+  let unrefCalls = 0\n+\n+  const child = {\n+    pid: 4242,\n+    unref: () => {\n+      unrefCalls += 1\n+    }\n+  }\n+\n+  const result = spawnUpdaterProcess(\n+    'hermes-setup.exe',\n+    ['--update', '--branch', 'main'],\n+    { cwd: 'C:\\\\Hermes', detached: true, stdio: 'ignore' },\n+    {\n+      isWindows: true,\n+      spawnProcess: (command, args, options) => {\n+        calls.push({ args, command, options })\n+\n+        return child\n+      }\n+    }\n+  )\n+\n+  assert.equal(result, child)\n+  assert.equal(unrefCalls, 1)\n+  assert.deepEqual(calls, [\n+    {\n+      args: ['--update', '--branch', 'main'],\n+      command: 'hermes-setup.exe',\n+      options: { cwd: 'C:\\\\Hermes', detached: true, stdio: 'ignore', windowsHide: true }\n+    }\n+  ])\n+})\n+\n+test('spawnUpdaterProcess preserves updater options off Windows', () => {\n+  let capturedOptions: SpawnOptions | undefined\n+\n+  spawnUpdaterProcess(\n+    'hermes-setup',\n+    ['--update'],\n+    { detached: true, stdio: 'ignore' },\n+    {\n+      isWindows: false,\n+      spawnProcess: (_command, _args, options) => {\n+        capturedOptions = options\n+\n+        return { unref: () => {} }\n+      }\n+    }\n+  )\n+\n+  assert.deepEqual(capturedOptions, { detached: true, stdio: 'ignore' })\n+})"]},{"file":"apps/desktop/electron/updater-process.ts","hunks":["@@ -0,0 +1,36 @@\n+import { spawn, type SpawnOptions } from 'node:child_process'\n+\n+import { hiddenWindowsChildOptions } from './windows-child-options'\n+\n+export interface UpdaterChild {\n+  pid?: number\n+  unref: () => void\n+}\n+\n+export interface SpawnUpdaterProcessDeps {\n+  isWindows?: boolean\n+  spawnProcess?: (command: string, args: string[], options: SpawnOptions) => UpdaterChild\n+}\n+\n+/**\n+ * Spawn the detached installer used for update and bootstrap-recovery handoffs.\n+ * The helper owns both hidden-console selection and unref semantics so every\n+ * updater handoff follows the same behavior and can be tested without Electron.\n+ */\n+export function spawnUpdaterProcess(\n+  updater: string,\n+  updaterArgs: string[],\n+  options: SpawnOptions,\n+  deps: SpawnUpdaterProcessDeps = {}\n+): UpdaterChild {\n+  const isWindows = deps.isWindows ?? process.platform === 'win32'\n+  const spawnOptions = hiddenWindowsChildOptions(options, isWindows) as SpawnOptions\n+\n+  const child = deps.spawnProcess\n+    ? deps.spawnProcess(updater, updaterArgs, spawnOptions)\n+    : spawn(updater, updaterArgs, spawnOptions)\n+\n+  child.unref()\n+\n+  return child\n+}\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}