fix(#685): set windowsHide on all Windows child-process spawns (#688)

* fix(#685): set windowsHide on all Windows child-process spawns

A visible "gsd-core" console window flashed on Windows whenever a gsd-core
child process spawned without `windowsHide: true`. The most visible offenders
fire on every SessionStart / `/clear` (execNpm's `shell:true` npm view via the
update-check worker) and on every Edit/Write/MultiEdit in a worktree (the
worktree-path guard's git probe).

Add `windowsHide: true` to every external-binary spawn in the runtime source:
- hooks/gsd-context-monitor.js (record-session spawn)
- hooks/gsd-worktree-path-guard.js (SPAWNOPT)
- hooks/gsd-workflow-guard.js (git branch --show-current)
- src/shell-command-projection.cts (execGit / execNpm / execTool)
- src/check-command-router.cts (git log execFileSync)
- src/roadmap-upgrade.cts (git status/rev-parse/reset/clean execSync)

gsd-check-update.js already had it (the precedent). probeTty's tty call is
POSIX-only and intentionally untouched. Adds a regression test that asserts
each site plus a repo-wide completeness guard so a future external-binary
spawn that omits windowsHide fails CI. No behavior change off-Windows.

Closes #685

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#685): set changeset pr number to 688

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-05 10:35:22 -04:00
committed by GitHub
parent 0e259a589c
commit 10f6981743
8 changed files with 115 additions and 6 deletions

View File

@@ -253,6 +253,7 @@ function recentCommitMessages(projectDir: string): string {
cwd: projectDir,
encoding: 'utf-8',
maxBuffer: 4 * 1024 * 1024,
windowsHide: true,
});
} catch {
return '';

View File

@@ -497,7 +497,7 @@ function applyMigration(cwd: string, plan: MigrationPlan, options: { dryRun?: bo
// ── Real run: verify clean working tree ───────────────────────────────────
let gitStatus: string;
try {
gitStatus = execSync('git status --porcelain', { cwd, encoding: 'utf8' });
gitStatus = execSync('git status --porcelain', { cwd, encoding: 'utf8', windowsHide: true });
} catch (err) {
throw new Error(`git status failed: ${(err as Error).message}`);
}
@@ -508,7 +508,7 @@ function applyMigration(cwd: string, plan: MigrationPlan, options: { dryRun?: bo
// Capture HEAD sha for rollback
let headSha: string;
try {
headSha = execSync('git rev-parse HEAD', { cwd, encoding: 'utf8' }).trim();
headSha = execSync('git rev-parse HEAD', { cwd, encoding: 'utf8', windowsHide: true }).trim();
} catch (err) {
throw new Error(`git rev-parse HEAD failed: ${(err as Error).message}`);
}
@@ -592,8 +592,8 @@ function applyMigration(cwd: string, plan: MigrationPlan, options: { dryRun?: bo
} catch (err) {
// Rollback via git reset --hard + git clean
try {
execSync(`git reset --hard ${headSha}`, { cwd, stdio: 'pipe' });
execSync('git clean -fd .planning/phases/', { cwd, stdio: 'pipe' });
execSync(`git reset --hard ${headSha}`, { cwd, stdio: 'pipe', windowsHide: true });
execSync('git clean -fd .planning/phases/', { cwd, stdio: 'pipe', windowsHide: true });
} catch {
// Swallow rollback errors — surface original error
}

View File

@@ -427,6 +427,7 @@ export function execGit(args: string[], opts: { cwd?: string; env?: Record<strin
encoding: 'utf-8',
stdio: 'pipe',
timeout: opts.timeout ?? 10_000,
windowsHide: true,
});
return _spawnResult(result, 'git');
}
@@ -438,6 +439,7 @@ export function execNpm(args: string[], opts: { cwd?: string; timeout?: number }
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'pipe'],
timeout: opts.timeout ?? 15_000,
windowsHide: true,
});
return _spawnResult(result, 'npm');
}
@@ -449,6 +451,7 @@ export function execTool(program: string, args: string[], opts: { cwd?: string;
encoding: 'utf-8',
stdio: 'pipe',
timeout: opts.timeout ?? 30_000,
windowsHide: true,
});
return _spawnResult(result, program);
}