fix(desktop): suppress Windows console flashes and reject WSL bash alias#2587
Merged
Conversation
Every background std::process::Command or tokio::process::Command spawn
from a GUI parent on Windows pops a visible console window unless
CREATE_NO_WINDOW (0x0800_0000) is set. Installs, probes, auth checks,
git commands, model-list calls, relay reconnects, media transcodes, and
ACP agent launches all had unguarded spawns.
Also: Git Bash resolution could select %LOCALAPPDATA%\Microsoft\WindowsApps\bash.exe,
a WSL stub that spawns wsl.exe / wslhost.exe / conhost.exe trees. Reject it.
## CREATE_NO_WINDOW coverage
desktop/src-tauri (shared helper in util::configure_no_window):
- commands/agent_auth.rs: acp auth command, Claude subscription login
- commands/agent_discovery/managed_node.rs: managed-node version probe
- commands/agent_model_process.rs: ACP model-listing spawn
- commands/media_transcode.rs: ffmpeg invoke
- commands/project_git_exec.rs: git exec
- commands/relay_reconnect.rs: relay probe
- managed_agents/backend.rs: provider invoke
- managed_agents/discovery.rs: login-shell PATH probe, auth status probe,
codex ACP version probe
- managed_agents/readiness/cli_probe.rs: CLI readiness probe
Excluded (legitimately need a visible console or macOS/Windows-only):
- commands/project_terminal.rs: launch_visible_terminal (explicit
CREATE_NEW_CONSOLE on Windows, terminal emulator on Linux)
- commands/agent_auth.rs: launch_visible_terminal call sites
- commands/window_chrome.rs: macOS 'defaults' read (macOS-only)
- managed_agents/process_lifecycle.rs: taskkill already sets CREATE_NO_WINDOW
- shutdown.rs: app relaunch (macOS-only, mesh-llm feature gate)
- managed_agents/agent_env.rs: Command::new("env") calls are test-only
- managed_agents/runtime.rs: agent spawn (PR 1 frozen file)
crates/buzz-acp (configure_no_window helper in acp.rs):
- acp.rs: AcpClient::spawn — agent subprocess launch
crates/buzz-dev-mcp (configure_no_window / configure_no_window_async in lib.rs):
- rg.rs: system rg invocation
- shell.rs: bash shell tool commands (tokio::process::Command)
## WindowsApps bash alias rejection
managed_agents/git_bash.rs:
- is_windows_apps_alias(path: &Path) -> bool — pure path-structural
predicate, cfg(any(windows, test)), testable on macOS CI
- scan_path_for_bash filters resolved paths through the predicate
- 6 cross-host tests in windows_apps_tests module
Fixes #2490, Fixes #2328, Fixes #2413
Supersedes #2493, #2416, #2488, #2541
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Three MUST FIX items resolved: 1. helpers-after-test-module lint: move configure_no_window in buzz-acp/src/acp.rs and desktop/src-tauri/src/util.rs to before their respective mod tests blocks; remove unused std::os::windows::process::CommandExt import from tokio variants in acp.rs and buzz-dev-mcp/src/lib.rs (tokio exposes creation_flags natively, no import needed). 2. MCP server children CREATE_NO_WINDOW: add configure_no_window helper and call it in spawn_one() in crates/buzz-agent/src/mcp.rs before TokioChildProcess::new, suppressing console flashes for Node-backed MCP servers on Windows. cfg-gated no-op off Windows. Cross-host regression test added. 3. buzz-dev-mcp WSL alias rejection: add is_windows_apps_alias pure predicate (cfg(any(windows,test)), component-wise case-insensitive) in shell.rs and wire it into scan_path_for_bash so the WindowsApps stub launcher is skipped during PATH iteration — alias-first/real- bash-second scenario selects the real one. Six cross-host predicate tests + two Windows resolver tests (alias-first/real-second and alias-only) added. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two defects cause visible console windows during normal Buzz operation on Windows.
1. Missing
CREATE_NO_WINDOWon background spawn sitesEvery
std::process::Commandortokio::process::Commandspawn of a console-subsystem binary from a GUI (non-console) parent on Windows allocates a fresh, briefly-visible console window per child unlessCREATE_NO_WINDOW(0x0800_0000) is set. Every probe, install, auth check, git command, agent launch, and MCP server spawn had unguarded spawns.2. WSL app-execution-alias footgun (issue #2328)
Git Bash resolution (both desktop-side and inside buzz-dev-mcp) could select
%LOCALAPPDATA%\Microsoft\WindowsApps\bash.exe, which is a WSL stub launcher. Running it spawnswsl.exe/wslhost.exe/conhost.exetrees instead of executing the intended shell command.Fix
Per-crate
configure_no_windowhelpers — one helper per crate, no cross-crate abstraction:desktop/src-tauri/src/util.rs:pub(crate) fn configure_no_window(command: &mut std::process::Command)— called at every background spawn site in the desktop cratecrates/buzz-acp/src/acp.rs:fn configure_no_window(cmd: &mut tokio::process::Command)— applied toAcpClient::spawncrates/buzz-dev-mcp/src/lib.rs:configure_no_window(std) +configure_no_window_async(tokio) — applied torg.rsandshell.rsrespectivelycrates/buzz-agent/src/mcp.rs:fn configure_no_window(cmd: &mut Command)— applied tospawn_one()beforeTokioChildProcess::new, suppressing console flashes for Node-backed MCP server childrenDesktop spawn sites covered (via
crate::util::configure_no_window):commands/agent_auth.rs: ACP auth command, Claude subscription logincommands/agent_discovery/managed_node.rs: managed-node version probecommands/agent_model_process.rs: ACP model-listing spawncommands/media_transcode.rs: ffmpeg invokecommands/project_git_exec.rs: git execcommands/relay_reconnect.rs: relay probemanaged_agents/backend.rs: provider invokemanaged_agents/discovery.rs: login-shell PATH probe, auth status probe, codex ACP version probemanaged_agents/readiness/cli_probe.rs: CLI readiness probeExplicitly excluded (legitimately want a visible console or platform-specific):
commands/project_terminal.rs:launch_visible_terminalusesCREATE_NEW_CONSOLEon Windowscommands/agent_auth.rsterminal launch sites: samecommands/window_chrome.rs: macOS-onlydefaultsreadmanaged_agents/process_lifecycle.rs:taskkillalready setsCREATE_NO_WINDOWshutdown.rs: macOS-only app relaunch (mesh-llm feature gate)managed_agents/agent_env.rs:Command::new("env")calls are test-onlymanaged_agents/runtime.rs: agent spawn (PR 1 frozen, disjoint file set)WSL bash alias rejection — both resolver paths protected:
Desktop (
managed_agents/git_bash.rs):is_windows_apps_alias(path: &Path) -> bool— pure path-structural predicate,cfg(any(windows, test)), testable cross-hostscan_path_for_bashfilters resolved paths through the predicate before returningbuzz-dev-mcp (
crates/buzz-dev-mcp/src/shell.rs):is_windows_apps_alias(path: &Path) -> bool— same pure predicate,cfg(any(windows, test))scan_path_for_bashnow does inline iteration filtering both%SystemRoot%andWindowsApps— alias-first/real-bash-second ordering selects the real one; alias-only returnsNoneNon-Windows impact
None. Every
configure_no_windowhelper is a no-op off-Windows (#[cfg(not(windows))] let _ = cmd;). Theis_windows_apps_aliasfunctions are#[cfg(any(windows, test))];scan_path_for_bashis Windows-only in production.Fixes #2490, Fixes #2328, Fixes #2413
Supersedes #2493, #2416, #2488, #2541 (Will closes those; don't touch them)