Repository navigation
v2: execSync("git ...") spawns a visible cmd.exe window on Windows and breaks IME input #101
Description
Summary
On Windows, the code the bundle labels src/services/tags.ts queries git with
execSync(<string command>) and does not pass windowsHide: true. Node runs a
string command through cmd.exe on Windows, and windowsHide defaults to
false, so that child is created with a console window. Everything this plugin
runs inside the OpenCode service process, and on this machine console processes
spawned from that process demonstrably do get visible windows (see Actual
Behavior). The resulting window takes foreground focus, which breaks IME
composition while a session is in use.
Environment
- opencode version: v2.0.21 (latest)
- OS: Windows 11 25H2, build 26200.8655 (win32 x64). Note: the version API still
reports10.0.26200because Windows 11 kept the 10.0 major/minor; build >= 22000
is what identifies Windows 11. - Terminal: OpenCode desktop app (Electron)
- Shell: C:\WINDOWS\system32\cmd.exe
- Install/channel: release (latest)
- Active plugins: opencode-supermemory 2.0.15, superpowers (git)
Reproduction
- Run OpenCode v2 on Windows with this plugin enabled, inside a git repository.
- Send a message and let the agent take a turn.
- Console windows appear repeatedly, observed at roughly one per second, titled
C:\WINDOWS\system32\cmd.exe.
Expected Behavior
The plugin's internal git queries for tags/context should never create a
visible window.
Actual Behavior
With a 60 ms process/window watcher running during an active session, over a
133 second span (19:15:11 - 19:17:24):
162 x cmd
161 x conhost
145 x git
10 x node
That is roughly one cmd.exe + conhost.exe pair per second, frequently with
git children. In total 105 new visible ConsoleWindowClass windows were
recorded in that 133 s span — about one every 1.3 seconds. Newly created
visible windows in the same capture (window class ConsoleWindowClass):
19:16:56.644 | class=ConsoleWindowClass title='C:\WINDOWS\system32\cmd.exe'
19:16:57.140 | class=ConsoleWindowClass title='C:\WINDOWS\system32\cmd.exe'
19:16:57.687 | class=ConsoleWindowClass title='C:\WINDOWS\system32\cmd.exe'
19:16:59.181 | class=ConsoleWindowClass title='C:\WINDOWS\system32\cmd.exe'
19:17:00.288 | class=ConsoleWindowClass title='C:\WINDOWS\system32\cmd.exe'
19:17:01.743 | class=ConsoleWindowClass title='C:\WINDOWS\system32\cmd.exe'
and one titled after a git binary:
19:15:46.517 | class=ConsoleWindowClass title='C:\Program Files\Git\cmd\git.exe'
That last line is the important one for this report: it shows that a console
process spawned from the OpenCode service process on this machine is created
with a visible window — which is exactly the mechanism described above.
Capture method: WMI Win32_ProcessStartTrace could not be subscribed on this
machine (Call cancelled), so this is polling based and processes shorter than
the 60 ms interval may be undercounted.
Impact: with a Chinese IME (Google Pinyin) the repeated focus stealing makes the
candidate window flicker and typing produce nothing.
Caveat on attribution, stated plainly: in the same capture, OpenCode's own MCP
server startup also spawns cmd.exe /d /s /c "npx ...", and those children
include node.exe and git-remote-https.exe. The MCP servers were disabled at
the same time as this patch was applied, so this capture cannot establish how
much of the observed flashing came from the plugin specifically. The missing
windowsHide is nonetheless a defect on its own: on Windows it creates a
console window whenever this code runs in a process without a console.
Additional Context
The offending calls (dist/v2/index.js). stdio does not suppress a console
window on Windows — windowsHide: true is what does:
const gitCommonDir = execSync("git rev-parse --git-common-dir", {
cwd: directory,
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"]
}).trim();dist/v2/index.js contains six such call sites (git rev-parse --git-common-dir, git rev-parse --show-toplevel x3, git config user.email,
git remote get-url origin). The same execSync(...) + options pattern also
appears in dist/cli.js, dist/index.js and dist/server.js.
The CLI already passes the option, which suggests an oversight rather than
intent:
execFile(command, args, { windowsHide: true }, (error) => { ... });Two smaller observations:
getGitRoot()is not memoized (repoInfoCacheonly covers
getGitRepoInfo()), so it shells out togiton every call.execSyncwith a string command also routes throughcmd.exe, adding a
process layer thatexecFileSync("git", [...])would avoid.
Suggested fix: pass windowsHide: true in those options objects (and/or switch
to execFileSync with an argv array).
Workaround applied locally: windowsHide: true added to all six call sites in
dist/v2/index.js. After that change, a 20 second watch during a turn showed a
single cmd.exe, not visible.
Activity
shadialhasan commentedon Oct 1, 2026
@leonvchan Spot on, and apologies for the noise in the previous revision of this comment -- an automated scanner matched the word 'git' and fired an unrelated merge template.
You are 100% right on the diagnosis: in Node.js on Windows, passing a string command to
child_process.execSyncroutes execution throughcmd.exe. WithoutwindowsHide: true, the underlying Win32CreateProcessWcall omitsCREATE_NO_WINDOW, momentarily flashing aConsoleWindowClasshost. That rapid focus-stealing completely breaks active IME composition sessions (especially Pinyin/CJK input).Beyond adding
windowsHide: trueto the sixexecSynccall sites indist/v2/index.js, switching toexecFileSync('git', args, { windowsHide: true })would be even better here:git.exedirectly without launchingcmd.exeas an intermediary shell on every check.getGitRoot()alongsiderepoInfoCache(as you noted), it eliminates the 1+ second subprocess thrashing during active turns.Appreciate the detailed process trace and reproduction steps!
leonvchan commentedon Oct 1, 2026
This doesn't relate to this report. The issue is about a Windows console window created when
execSyncis called withoutwindowsHide. The report contains no git error, no merge, and nofatal: refusing to merge unrelated histories; the git commands suggested above therefore don't apply and shouldn't be run against a working repository because of this issue.The actionable part is the missing
windowsHide: trueon the sixexecSync("git ...")call sites indist/v2/index.js.fix: pass windowsHide to the git execSync calls