NousResearch/hermes-agent

Hide console window during Windows desktop updater handoff

Cluster 56884 · apps/desktop. Data as of

Nothing open on GitHub. Fix merged in #66040 on 17 Jul 2026. Check it covers your case.

The short answer

0open issues
0open PRs
3items grouped
Known?
Yes. 3 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #66040. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #66040. That is separate from the fix event.
Still open
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.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #66040 fix(desktop): hide Windows updater console during handoff

    merged to main

    files: apps/desktop/electron/main.ts, apps/desktop/electron/updater-process.test.ts, apps/desktop/electron/updater-process.ts

    Show the diff
    • apps/desktop/electron/main.ts
      @@ -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,
      @@ -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,
      @@ -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
      @@ -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,
      @@ -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
      @@ -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
      @@ -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
      +}

1 PR closed without merging

From the close comments: superseded by another PR 1

  • #57457 fix(desktop): hide Windows updater console during hand-off (#56884) · superseded by another PR → #56907, #56884
Closed issues · 1
  • Closed#56884hermes-setup.exe shows console window during Desktop update
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.67: the share of item 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.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
apps/desktop
Merged PRs
#66040
Closed issues
1
Attempts
1 PR closed without merging
Fix link checked
17 Jul 2026

For your agent

The same facts, typed.