# NousResearch/hermes-agent · Hide console window during Windows desktop updater handoff

## Open now

- Nothing open on GitHub.

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.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #66040. Verify it addresses your case.
- **Recommended:** read PR #66040 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-66040`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #56884: hermes-setup.exe shows console window during Desktop update

## Prior & competing PRs

- **#66040: fix(desktop): hide Windows updater console during handoff**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `apps/desktop/electron/main.ts`, `apps/desktop/electron/updater-process.test.ts`, `apps/desktop/electron/updater-process.ts`
  - fix hunks:
  - `apps/desktop/electron/main.ts`

    ```diff
    @@ -126,6 +126,7 @@ import {
       sandboxPreflight
     } from './update-relaunch'
     import { isOfficialSshRemote, OFFICIAL_REPO_HTTPS_URL } from './update-remote'
    +import { spawnUpdaterProcess } from './updater-process'
     import { fetchMarketplaceThemes, searchMarketplaceThemes } from './vscode-marketplace'
     import {
       computeWindowOptions,
    ```

    ```diff
    @@ -2520,7 +2521,7 @@ async function applyUpdates(opts = {}) {
     
         // Detached so the updater outlives this process — it needs us GONE before
         // `hermes update` will run (the venv shim is locked while we live).
    -    const child = spawn(updater, updaterArgs, {
    +    const child = spawnUpdaterProcess(updater, updaterArgs, {
           cwd: HERMES_HOME,
           env: {
             ...process.env,
    ```

    ```diff
    @@ -2528,12 +2529,9 @@ async function applyUpdates(opts = {}) {
             PATH: pathWithHermesManagedNode(venvBin)
           },
           detached: true,
    -      stdio: 'ignore',
    -      windowsHide: false
    +      stdio: 'ignore'
         })
     
    -    child.unref()
    -
         // Write the update-in-progress marker IMMEDIATELY — before the 2.5s
         // quit dwell. The Tauri updater won't write its own marker for several
         // seconds (window init + manifest), and during that gap our renderer
    ```

    ```diff
    @@ -2598,7 +2596,7 @@ async function handOffWindowsBootstrapRecovery(reason) {
     
       await releaseBackendLockForUpdate(updateRoot)
     
    -  const child = spawn(updater, updaterArgs, {
    +  const child = spawnUpdaterProcess(updater, updaterArgs, {
         cwd: HERMES_HOME,
         env: {
           ...process.env,
    ```

    ```diff
    @@ -2606,12 +2604,9 @@ async function handOffWindowsBootstrapRecovery(reason) {
           PATH: pathWithHermesManagedNode(venvBin)
         },
         detached: true,
    -    stdio: 'ignore',
    -    windowsHide: false
    +    stdio: 'ignore'
       })
     
    -  child.unref()
    -
       // Same marker pre-write as applyUpdates — see comment there. The recovery
       // hand-off has the same window where the renderer can respawn a backend
       // before the updater writes its own marker.
    ```
  - `apps/desktop/electron/updater-process.test.ts`

    ```diff
    @@ -0,0 +1,62 @@
    +import assert from 'node:assert/strict'
    +import type { SpawnOptions } from 'node:child_process'
    +
    +import { test } from 'vitest'
    +
    +import { spawnUpdaterProcess } from './updater-process'
    +
    +test('spawnUpdaterProcess hides the updater console and detaches the child on Windows', () => {
    +  const calls: Array<{ args: string[]; command: string; options: SpawnOptions }> = []
    +  let unrefCalls = 0
    +
    +  const child = {
    +    pid: 4242,
    +    unref: () => {
    +      unrefCalls += 1
    +    }
    +  }
    +
    +  const result = spawnUpdaterProcess(
    +    'hermes-setup.exe',
    +    ['--update', '--branch', 'main'],
    +    { cwd: 'C:\\Hermes', detached: true, stdio: 'ignore' },
    +    {
    +      isWindows: true,
    +      spawnProcess: (command, args, options) => {
    +        calls.push({ args, command, options })
    +
    +        return child
    +      }
    +    }
    +  )
    +
    +  assert.equal(result, child)
    +  assert.equal(unrefCalls, 1)
    +  assert.deepEqual(calls, [
    +    {
    +      args: ['--update', '--branch', 'main'],
    +      command: 'hermes-setup.exe',
    +      options: { cwd: 'C:\\Hermes', detached: true, stdio: 'ignore', windowsHide: true }
    +    }
    +  ])
    +})
    +
    +test('spawnUpdaterProcess preserves updater options off Windows', () => {
    +  let capturedOptions: SpawnOptions | undefined
    +
    +  spawnUpdaterProcess(
    +    'hermes-setup',
    +    ['--update'],
    +    { detached: true, stdio: 'ignore' },
    +    {
    +      isWindows: false,
    +      spawnProcess: (_command, _args, options) => {
    +        capturedOptions = options
    +
    +        return { unref: () => {} }
    +      }
    +    }
    +  )
    +
    +  assert.deepEqual(capturedOptions, { detached: true, stdio: 'ignore' })
    +})
    ```
  - `apps/desktop/electron/updater-process.ts`

    ```diff
    @@ -0,0 +1,36 @@
    +import { spawn, type SpawnOptions } from 'node:child_process'
    +
    +import { hiddenWindowsChildOptions } from './windows-child-options'
    +
    +export interface UpdaterChild {
    +  pid?: number
    +  unref: () => void
    +}
    +
    +export interface SpawnUpdaterProcessDeps {
    +  isWindows?: boolean
    +  spawnProcess?: (command: string, args: string[], options: SpawnOptions) => UpdaterChild
    +}
    +
    +/**
    + * Spawn the detached installer used for update and bootstrap-recovery handoffs.
    + * The helper owns both hidden-console selection and unref semantics so every
    + * updater handoff follows the same behavior and can be tested without Electron.
    + */
    +export function spawnUpdaterProcess(
    +  updater: string,
    +  updaterArgs: string[],
    +  options: SpawnOptions,
    +  deps: SpawnUpdaterProcessDeps = {}
    +): UpdaterChild {
    +  const isWindows = deps.isWindows ?? process.platform === 'win32'
    +  const spawnOptions = hiddenWindowsChildOptions(options, isWindows) as SpawnOptions
    +
    +  const child = deps.spawnProcess
    +    ? deps.spawnProcess(updater, updaterArgs, spawnOptions)
    +    : spawn(updater, updaterArgs, spawnOptions)
    +
    +  child.unref()
    +
    +  return child
    +}
    ```
- **#57457: fix(desktop): hide Windows updater console during hand-off (#56884)**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#56907`, `#56884`

## Fix status

- Merged to main in PR #66040 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-07-17`.
- Subsystem: `apps/desktop`
