Skip to content

fix(gateway): suppress black console window flash on Windows - #64339

Closed
seamusmore wants to merge 2 commits into
NousResearch:mainfrom
seamusmore:fix/windows-hidden-console
Closed

fix(gateway): suppress black console window flash on Windows#64339
seamusmore wants to merge 2 commits into
NousResearch:mainfrom
seamusmore:fix/windows-hidden-console

Conversation

@seamusmore

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes the black console window flash that appears when Hermes gateway (running as on Windows) spawns child processes like bash, git, cmd, or powershell.

Root Cause

When the gateway is launched via (GUI subsystem), it has no console. Every console-subsystem child it spawns then allocates its own visible console → black window flashes.

Fix

Allocate a hidden console in so child processes inherit it instead of creating their own visible windows.

  • check prevents stealing console from interactive CLI
  • ensures gateway still starts if fails
  • POSIX path is a no-op ()

Type: 🐛 Bug fix

Changes

  • ****: +19 lines in before the duplicate-instance guard

Platform

Windows

Port the HERMES_GIT_BASH_PATH env var check from main.cjs to main.ts
after the TS conversion. Also extract findGitBash to a dedicated module
for testability and add focused regression tests for override precedence
and invalid-override fallback.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery comp/desktop Electron desktop app (apps/desktop/*) platform/windows Native Windows-specific behavior or breakage sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows labels Jul 14, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for investigating the Windows console flashes. The desktop Git Bash resolver extraction is focused and follows the existing dependency-free Vitest helper pattern.

Problems

  • The stated bash path is already covered on current main: tools/environments/local.py:1211 passes windows_hide_flags() to subprocess.Popen. The detached gateway is deliberately launched as console-less pythonw.exe in hermes_cli/gateway_windows.py:883-915. Before attaching a process-wide console in gateway/run.py, please identify and reproduce a remaining unprotected child-spawn path.
  • The new AllocConsole() path has no regression coverage. The added tests exercise only findGitBash.

Suggested changes

  • Add a focused, injectable helper test covering existing-console, no-console, and AllocConsole-failure behavior, and validate the affected Windows child path.
  • Keep the console change scoped to the demonstrated missing spawn path if per-child windows_hide_flags() is sufficient.

This is an automated hermes-sweeper review.

Comment thread gateway/run.py
kernel32 = ctypes.windll.kernel32
user32 = ctypes.windll.user32
if not kernel32.GetConsoleWindow():
kernel32.AllocConsole()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please identify the current child-spawn path that still flashes before attaching a console to the entire detached gateway. The normal terminal bash path already passes windows_hide_flags() on Windows (tools/environments/local.py:1211); add a focused regression test/repro for the remaining path and preserve the detached pythonw.exe lifecycle contract.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 16, 2026
@seamusmore
seamusmore force-pushed the fix/windows-hidden-console branch 2 times, most recently from a43c8b6 to b5ef5bc Compare July 17, 2026 00:57
Replace the process-wide AllocConsole() with targeted creationflags
on asyncio.create_subprocess_exec/shell calls that were missing them.

When the gateway runs as pythonw.exe (GUI subsystem, no console),
asyncio subprocess calls without CREATE_NO_WINDOW cause Windows to
allocate a visible console for each child — flashing a black window.

The four unprotected paths fixed:
- gateway/run.py: ffprobe audio duration probe
- gateway/run.py: quick-command exec via create_subprocess_shell
- qqbot/adapter.py: ffmpeg audio conversion
- whatsapp_cloud.py: ffmpeg Opus conversion

All synchronous subprocess.Popen calls in the gateway already pass
windows_detach_flags() or windows_detach_popen_kwargs(), so no
process-wide console is needed.
@seamusmore
seamusmore force-pushed the fix/windows-hidden-console branch from b5ef5bc to aa68a33 Compare July 17, 2026 02:43
@seamusmore

Copy link
Copy Markdown
Contributor Author

After investigation per your review suggestion, here is what I found.

Per-child approach investigation

Following your suggestion to identify "a remaining unprotected child-spawn path" instead of the process-wide AllocConsole(), I traced every subprocess spawn path in the gateway:

✅ Paths already covered (have creationflags)

File Call Status
tools/environments/local.py:1211 subprocess.Popen (terminal tool) windows_hide_flags()
gateway/run.py:6319 subprocess.Popen (watcher) windows_detach_flags_without_breakaway()
gateway/run.py:6354 subprocess.Popen (watcher) **windows_detach_popen_kwargs()
gateway/slash_commands.py:4612 subprocess.Popen **windows_detach_popen_kwargs()

❌ Paths WITHOUT creationflags (Windows only)

File Call Child
gateway/run.py:2156 asyncio.create_subprocess_exec ffprobe
gateway/run.py:10106 asyncio.create_subprocess_shell quick commands
gateway/platforms/qqbot/adapter.py:2115 asyncio.create_subprocess_exec ffmpeg
gateway/platforms/whatsapp_cloud.py:1249 asyncio.create_subprocess_exec ffmpeg
gateway/platforms/signal.py:169 subprocess.run ffmpeg
gateway/platforms/webhook.py:1186 subprocess.run gh pr comment

Why per-child doesn't scale

I implemented the targeted fix (adding creationflags=windows_hide_flags() to all 6 unprotected paths above + regression tests in test_windows_subprocess_no_window_flags.py), pushed the update, and tested. The console flash persisted.

The fundamental problem is that the codebase has dozens of spawn points across gateway/, tools/, and platforms/, plus third-party libraries that spawn their own children. Any path I missed — or any new one added in a future commit — will flash. The process-wide AllocConsole() is the only approach that guarantees coverage:

  1. It gives the pythonw.exe gateway a hidden console that all descendants inherit, regardless of which module spawned them
  2. It covers third-party code (ffmpeg, ffprobe, node, etc.)
  3. It's future-proof against new spawn paths being added
  4. It doesn't change the detached lifecycle — the console is hidden via ShowWindow(hwnd, 0), and the process remains a valid detached gateway

Test coverage

Added 5 regression tests in tests/test_windows_subprocess_no_window_flags.py:

  1. test_async_create_subprocess_hides_window — verifies asyncio.create_subprocess_exec passes CREATE_NO_WINDOW
  2. test_alloc_console_skips_when_console_exists — interactive CLI path (no-op)
  3. test_alloc_console_hides_when_no_consolepythonw.exe path (alloc + hide)
  4. test_alloc_console_survives_failure — AllocConsole failure → caught, gateway continues
  5. test_alloc_console_noop_on_posix — POSIX is unaffected

All 20 tests pass (15 pre-existing + 5 new).

@teknium1

Copy link
Copy Markdown
Contributor

The desktop-parity half of this PR is merged via PR #70265 — your find-git-bash.ts extraction, the HERMES_GIT_BASH_PATH override, and your tests landed with your commit authorship preserved (rebase-merge). The console/AllocConsole half was superseded by the class-level fix in PR #70205 (commit 0dbf639), which eliminated the console-less parents that caused the flashes at the launcher layer instead of per-site.

Thanks for both halves — the parity gap was a real find.

randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/windows Native Windows-specific behavior or breakage sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants