Skip to content

Commit ad139a9

Browse files
h5kkclaude
andcommitted
windows: extract suppress_child_console helper, sweep server-side spawns
Builds on c24727f (which suppressed the cmd.exe window in tool::bash::build_shell_command) by extracting a reusable `platform::suppress_child_console{,_async}` helper and applying it to the other server-side spawns of console-subsystem programs. Same root cause: the server runs `DETACHED_PROCESS`, has no inherited console, and Windows allocates a fresh console window for any console-subsystem child unless `CREATE_NO_WINDOW` (0x08000000) is set. Stdio is already piped on every site touched, so the suppression is purely cosmetic (no functional change to tool I/O). Sites swept: - src/tool/bash.rs: refactored inline flag from c24727f to call helper. - src/server/client_actions.rs: input shell command (cmd.exe /C ...). - src/tool/ambient.rs: git rev-parse probe in ambient runner. - src/tool/selfdev/status.rs: git status --porcelain. - src/tool/selfdev/build_queue.rs: build streaming command (cargo etc). - src/agent/utils.rs: git_output (used by image side-panel hooks). The helper is a no-op on non-Windows platforms via cfg gates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c24727f commit ad139a9

7 files changed

Lines changed: 52 additions & 22 deletions

File tree

src/agent/utils.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,10 @@ pub(super) fn git_state_for_dir(dir: &Path) -> Option<GitState> {
2929
}
3030

3131
fn git_output(dir: &Path, args: &[&str]) -> Option<String> {
32-
let output = Command::new("git")
33-
.args(args)
34-
.current_dir(dir)
35-
.output()
36-
.ok()?;
32+
let mut cmd = Command::new("git");
33+
cmd.args(args).current_dir(dir);
34+
crate::platform::suppress_child_console(&mut cmd);
35+
let output = cmd.output().ok()?;
3736
if !output.status.success() {
3837
return None;
3938
}

src/platform.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,41 @@ pub fn spawn_detached(cmd: &mut std::process::Command) -> std::io::Result<std::p
372372
cmd.spawn()
373373
}
374374

375+
/// Suppress allocation of a fresh console window for a child process on Windows.
376+
///
377+
/// When the jcode server runs detached (`DETACHED_PROCESS`, no inherited
378+
/// console) and spawns a console-subsystem program (`cmd.exe`, `git`,
379+
/// `cargo`, `python`, ...), Windows allocates a brand-new console window for
380+
/// the child unless we set `CREATE_NO_WINDOW` (`0x08000000`). That manifests
381+
/// as PowerShell/cmd windows flashing up on every Bash tool call, every
382+
/// background git/cargo probe, etc.
383+
///
384+
/// Use this on every `std::process::Command` spawned from server-side code
385+
/// where the child's stdio is already piped (so a console window would be
386+
/// pure noise). For `tokio::process::Command`, see
387+
/// [`suppress_child_console_async`].
388+
///
389+
/// On non-Windows platforms this is a no-op.
390+
#[cfg_attr(not(windows), allow(unused_variables))]
391+
pub fn suppress_child_console(cmd: &mut std::process::Command) {
392+
#[cfg(windows)]
393+
{
394+
use std::os::windows::process::CommandExt;
395+
const CREATE_NO_WINDOW: u32 = 0x08000000;
396+
cmd.creation_flags(CREATE_NO_WINDOW);
397+
}
398+
}
399+
400+
/// Tokio variant of [`suppress_child_console`].
401+
#[cfg_attr(not(windows), allow(unused_variables))]
402+
pub fn suppress_child_console_async(cmd: &mut tokio::process::Command) {
403+
#[cfg(windows)]
404+
{
405+
const CREATE_NO_WINDOW: u32 = 0x08000000;
406+
cmd.creation_flags(CREATE_NO_WINDOW);
407+
}
408+
}
409+
375410
#[cfg(windows)]
376411
fn spawn_replacement_process(
377412
cmd: &mut std::process::Command,

src/server/client_actions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ fn build_input_shell_command(command: &str) -> Command {
3939
{
4040
let mut cmd = Command::new("cmd.exe");
4141
cmd.arg("/C").arg(command);
42+
crate::platform::suppress_child_console_async(&mut cmd);
4243
cmd
4344
}
4445

src/tool/ambient.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -802,11 +802,11 @@ impl Tool for ScheduleTool {
802802
.working_dir
803803
.as_ref()
804804
.and_then(|wd| {
805-
std::process::Command::new("git")
806-
.args(["rev-parse", "--abbrev-ref", "HEAD"])
807-
.current_dir(wd)
808-
.output()
809-
.ok()
805+
let mut cmd = std::process::Command::new("git");
806+
cmd.args(["rev-parse", "--abbrev-ref", "HEAD"])
807+
.current_dir(wd);
808+
crate::platform::suppress_child_console(&mut cmd);
809+
cmd.output().ok()
810810
})
811811
.and_then(|out| {
812812
if out.status.success() {

src/tool/bash.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -436,14 +436,9 @@ async fn handle_background_output_line(
436436
fn build_shell_command(cmd_str: &str) -> TokioCommand {
437437
#[cfg(windows)]
438438
{
439-
// CREATE_NO_WINDOW (0x08000000): cmd.exe is a console-subsystem program;
440-
// when the server runs detached (no inherited console), Windows would
441-
// otherwise allocate a fresh console window for every Bash tool call,
442-
// which flashes up as a stray PowerShell/cmd window. Suppressing the
443-
// window has no effect on stdio (we pipe stdout/stderr above).
444-
const CREATE_NO_WINDOW: u32 = 0x08000000;
445439
let mut cmd = TokioCommand::new("cmd.exe");
446-
cmd.arg("/C").arg(cmd_str).creation_flags(CREATE_NO_WINDOW);
440+
cmd.arg("/C").arg(cmd_str);
441+
crate::platform::suppress_child_console_async(&mut cmd);
447442
cmd
448443
}
449444
#[cfg(not(windows))]

src/tool/selfdev/build_queue.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ impl SelfDevTool {
6161
.kill_on_drop(true)
6262
.stdout(Stdio::piped())
6363
.stderr(Stdio::piped());
64+
crate::platform::suppress_child_console_async(&mut cmd);
6465

6566
let mut child = cmd
6667
.spawn()

src/tool/selfdev/status.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ pub fn selfdev_status_output() -> Result<ToolOutput> {
99
status.push_str(&format!("**Running:** jcode {}\n", env!("JCODE_VERSION")));
1010

1111
if let Some(repo_dir) = build::get_repo_dir() {
12-
let output = std::process::Command::new("git")
13-
.args(["status", "--porcelain"])
14-
.current_dir(&repo_dir)
15-
.output()
16-
.ok();
12+
let mut cmd = std::process::Command::new("git");
13+
cmd.args(["status", "--porcelain"]).current_dir(&repo_dir);
14+
crate::platform::suppress_child_console(&mut cmd);
15+
let output = cmd.output().ok();
1716

1817
if let Some(output) = output {
1918
let changes: Vec<&str> = std::str::from_utf8(&output.stdout)

0 commit comments

Comments
 (0)