Skip to content
Closed
9 changes: 9 additions & 0 deletions agent/proxy_sources/iron_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
from pathlib import Path
from typing import Dict, List, Optional, Tuple

from hermes_cli._subprocess_compat import windows_hide_flags

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -605,6 +607,7 @@ def _verify_checksums_signature(tmp: Path, checksum_path: Path) -> bool:
imp = subprocess.run( # noqa: S603 — gpg path from trusted PATH lookup
[*base_cmd, "--import", str(pubkey_path)],
capture_output=True, timeout=60,
creationflags=windows_hide_flags(),
)
if imp.returncode != 0:
logger.warning(
Expand All @@ -617,6 +620,7 @@ def _verify_checksums_signature(tmp: Path, checksum_path: Path) -> bool:
verify = subprocess.run( # noqa: S603
[*base_cmd, "--verify", str(sig_path), str(checksum_path)],
capture_output=True, timeout=60,
creationflags=windows_hide_flags(),
)
if verify.returncode != 0:
# A present signature that does NOT verify is a tamper signal — fail hard.
Expand Down Expand Up @@ -709,6 +713,7 @@ def iron_proxy_version(binary: Path) -> str:
text=True, encoding="utf-8", errors="replace",
timeout=_RUN_TIMEOUT,
env=minimal_env,
creationflags=windows_hide_flags(),
)
except (OSError, subprocess.TimeoutExpired):
return ""
Expand Down Expand Up @@ -761,6 +766,7 @@ def ensure_ca_cert(*, force: bool = False) -> Tuple[Path, Path]:
check=True,
capture_output=True,
timeout=60,
creationflags=windows_hide_flags(),
)
subprocess.run( # noqa: S603
[
Expand All @@ -775,6 +781,7 @@ def ensure_ca_cert(*, force: bool = False) -> Tuple[Path, Path]:
check=True,
capture_output=True,
timeout=60,
creationflags=windows_hide_flags(),
)

# Move into place with private permissions. CRITICAL: the key
Expand Down Expand Up @@ -1744,6 +1751,7 @@ def _pid_alive(pid: int) -> bool:
res = subprocess.run( # noqa: S603
["ps", "-p", str(pid), "-o", "comm="],
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=2,
creationflags=windows_hide_flags(),
)
if res.returncode == 0:
comm = (res.stdout or "").strip()
Expand Down Expand Up @@ -1871,6 +1879,7 @@ def start_proxy(
stdin=subprocess.DEVNULL,
stdout=log_fd,
stderr=subprocess.STDOUT,
creationflags=windows_hide_flags(),
)
if platform.system() != "Windows":
popen_kwargs["start_new_session"] = True
Expand Down
2 changes: 2 additions & 0 deletions agent/secret_sources/bitwarden.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
)
from agent.secret_sources.base import ErrorKind, SecretSource
from agent.secret_sources.base import get_source_environment
from hermes_cli._subprocess_compat import windows_hide_flags

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -696,6 +697,7 @@ def _run_bws_list(
text=True, encoding='utf-8', errors='replace',
timeout=_BWS_RUN_TIMEOUT,
stdin=subprocess.DEVNULL,
creationflags=windows_hide_flags(),
)
except subprocess.TimeoutExpired as exc:
raise RuntimeError(
Expand Down
2 changes: 2 additions & 0 deletions agent/secret_sources/onepassword.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
)
from agent.secret_sources.base import ErrorKind, SecretSource
from agent.secret_sources.base import get_source_environment
from hermes_cli._subprocess_compat import windows_hide_flags

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -287,6 +288,7 @@ def _run_op_read(
encoding="utf-8",
errors="replace",
timeout=_OP_RUN_TIMEOUT,
creationflags=windows_hide_flags(),
)
except subprocess.TimeoutExpired as exc:
raise RuntimeError(
Expand Down
41 changes: 40 additions & 1 deletion apps/desktop/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2329,15 +2329,22 @@ function resolveGitBinary() {
const localAppData = process.env.LOCALAPPDATA || ''
const candidates = []

// Prefer the real `bin\git.exe` over the `cmd\git.exe` shim: the shim
// re-execs the real git in a fresh console, which flashes a visible window
// on Windows even when the spawn passed `windowsHide`. The real binary
// honours the hidden-console flag, so it must come first.
if (localAppData) {
candidates.push(path.join(localAppData, 'hermes', 'git', 'cmd', 'git.exe'))
candidates.push(path.join(localAppData, 'hermes', 'git', 'bin', 'git.exe'))
candidates.push(path.join(localAppData, 'hermes', 'git', 'cmd', 'git.exe'))
}

candidates.push(path.join(process.env['ProgramFiles'] || 'C:\\Program Files', 'Git', 'bin', 'git.exe'))
candidates.push(path.join(process.env['ProgramFiles'] || 'C:\\Program Files', 'Git', 'cmd', 'git.exe'))
candidates.push(path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Git', 'bin', 'git.exe'))
candidates.push(path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Git', 'cmd', 'git.exe'))

if (localAppData) {
candidates.push(path.join(localAppData, 'Programs', 'Git', 'bin', 'git.exe'))
candidates.push(path.join(localAppData, 'Programs', 'Git', 'cmd', 'git.exe'))
}

Expand Down Expand Up @@ -2936,9 +2943,14 @@ async function processStartMarker(pid) {
}

if (IS_WINDOWS) {
// -WindowStyle Hidden is redundant with execText's windowsHide:true, but
// windowsHide alone is not always reliable for `powershell.exe -Command`
// specifically (a known Node/Windows quirk) -- belt-and-suspenders since
// this fires up to 3x in rapid succession during every desktop startup.
const ticks = await execText('powershell.exe', [
'-NoProfile',
'-NonInteractive',
'-WindowStyle', 'Hidden',
'-Command',
`$p = Get-Process -Id ${pid} -ErrorAction Stop; $p.StartTime.ToUniversalTime().Ticks`
])
Expand Down Expand Up @@ -10221,6 +10233,33 @@ function createWindow() {
mainWindow.on('hide', () => sendWindowStateChanged())
mainWindow.on('show', () => sendWindowStateChanged())

// Windows: a frameless window with titleBarOverlay can repaint its
// Windows-Control-Overlay region with the raw native (unstyled black)
// frame for one compositor frame when DWM redraws on WM_ACTIVATE --
// most visible when refocusing an already-open window (taskbar click,
// alt-tab), not just on first show. Re-pushing the overlay options on
// focus forces Chromium to redraw that strip with the correct themed
// colors immediately instead of leaving the stale native frame visible.
// applyTitleBarOverlay already no-ops safely on platforms/builds where
// this isn't applicable.
//
// Debounced: Electron fires 'focus' multiple times during a window's own
// startup/show sequence (creation, internal focus transitions between
// helper windows), not just on later refocus. Without debouncing, this
// handler repainted the titlebar strip 3-4x in a burst right at launch --
// more visible flicker than the bug it was meant to fix. Collapsing to
// at most one repaint per second keeps the later-refocus fix intact
// while eliminating the launch-time burst.
let lastTitleBarRepaint = 0
mainWindow.on('focus', () => {
const now = Date.now()
if (now - lastTitleBarRepaint < 1000) {
return
}
lastTitleBarRepaint = now
applyTitleBarOverlay(mainWindow)
})

// Reopen where the user left off. close is the backstop, flushed
// synchronously before the window is gone.
bindGeometryPersistence(mainWindow, schedulePersistWindowState)
Expand Down
6 changes: 4 additions & 2 deletions apps/desktop/electron/ssh-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import net from 'node:net'
import os from 'node:os'
import path from 'node:path'

import { hiddenWindowsChildOptions } from './windows-child-options'

const DEFAULT_CONNECT_TIMEOUT_MS = 15_000
const DEFAULT_EXEC_TIMEOUT_MS = 20_000
const DEFAULT_FORWARD_TIMEOUT_MS = 15_000
Expand Down Expand Up @@ -357,7 +359,7 @@ function runSsh(args, { timeoutMs, spawnFn = spawn, stdin = 'ignore', stdinData
let child

try {
child = spawnFn('ssh', args, { stdio: [useStdinPipe ? 'pipe' : 'ignore', 'pipe', 'pipe'] })
child = spawnFn('ssh', args, hiddenWindowsChildOptions({ stdio: [useStdinPipe ? 'pipe' : 'ignore', 'pipe', 'pipe'] }))
} catch (error) {
reject(error)

Expand Down Expand Up @@ -720,7 +722,7 @@ class SshConnection {
target(this.user, this.host)
]

const child = this._spawnFn('ssh', args, { stdio: ['ignore', 'ignore', 'pipe'] })
const child = this._spawnFn('ssh', args, hiddenWindowsChildOptions({ stdio: ['ignore', 'ignore', 'pipe'] }))
const tunnel = { child, alive: true }
this._tunnels.set(spec, tunnel)
let stderr = ''
Expand Down
Binary file added bin/hermes-acp.exe
Binary file not shown.
Binary file added bin/hermes.exe
Binary file not shown.
Loading