Skip to content

fix(opencode2): hide background console windows on windows - #45259

Closed
gabparrot wants to merge 1 commit into
anomalyco:v2from
gabparrot:hide-windows-console
Closed

gabparrot wants to merge 1 commit into
anomalyco:v2from
gabparrot:hide-windows-console

Conversation

@gabparrot

@gabparrot gabparrot commented Aug 26, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #42440
Fixes #42440

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

On Windows, spawnServiceContender starts the background service as a detached process (DETACHED_PROCESS). Because the detached service has no console attached, Windows kernel allocates a new visible console window (ConsoleWindowClass / conhost.exe) whenever the service spawns a child process (such as tool calls or background updater checks).

Depending on the user's settings, this will either translate into a black window appearing and disappearing after 40-150ms, or a new tab in the currently active terminal, taking focus and then disappearing, bringing the user to the last tab instead of the currently used tab

This PR adds windowsHide: true (CREATE_NO_WINDOW) to spawnServiceContender and internal desktop helper spawns while keeping detached: true, so the service starts without creating console windows and still persists across spawner CLI process exits.

This problem is at its worse when OpenCode 2 was started from Powershell 7.x (pwsh.exe)

This problem is new to Opencode 2, not present in Opencode 1

How did you verify your code works?

  1. Added packages/client/test/service-survival.test.ts to assert that the spawned service contender survives parent process exit and receives environment variables (bun test test/service-survival.test.ts passes).
  2. Ran bun typecheck across all packages (0 errors).
  3. Verified on Windows 11 with EnumWindows scanning during CLI and tool executions — zero visible console windows appear.

Screenshots / recordings

Difficult to screenshot as it only appears for 40 ms, but it takes focus. When it's a tab, it doesnt bring you back to your initial focused tab, but to the last one instead.
image

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@neriousy

Copy link
Copy Markdown
Member

I can't reproduce this locally. Are you using any plugins?

@gabparrot gabparrot closed this Aug 26, 2026
@gabparrot gabparrot reopened this Aug 26, 2026
@gabparrot

Copy link
Copy Markdown
Author

here my current setup for bug replication if you dont have it natively on your setup:

  • On windows 11
  • Open terminal, go to settings
  • Set Powershell 7 as the default profile (not microsoft powershell 5.x)
  • Set Microsoft Console Host as the default new terminal application
  • pwsh.exe (powershell 7+)
  • opencode2
  • Ask any claude or gpt model to grep "hello world" in the sub folders
  • Minimize to desktop to be sure it doesnt spawn under your window

I was not able to replicate it on Linux.

@gabparrot
gabparrot marked this pull request as draft August 26, 2026 15:43
@gabparrot

Copy link
Copy Markdown
Author

Drafting it, still able to replicate the bug in some cases

@gabparrot

Copy link
Copy Markdown
Author

prompt was just "grep for hello world in this folder"
https://github.com/user-attachments/assets/b89642d0-58f9-4bfd-a88a-d01f7fde6781

@PVLPM

PVLPM commented Sep 20, 2026

Copy link
Copy Markdown

Still reproducible on v2.0.11 (Windows 11, Desktop build). Worth flagging that there is a second, independent spawn path that this patch does not cover, so hiding spawnServiceContender alone won't make the symptom go away.

The path I can reproduce deterministically is local stdio MCP launchers. With npx-based MCP entries in opencode.json:

opencode-cli.exe serve --service
└─ cmd.exe /d /s /c "npx -y @playwright/mcp@latest --config <path>"
   └─ node.exe .../npm/bin/npx-cli.js
      └─ cmd.exe /d /s /c playwright-mcp --config <path>

Two cmd.exe per MCP server, and — separate bug — they are never reaped. On a normal session I ended up with 81 live cmd.exe: 45 direct children of opencode-cli.exe (12 × magicui, 12 × playwright, 12 × codegraph, 9 × shadcn) and 34 children of npx-cli.js. Because they are never reaped, the effect isn't the 40–150 ms flash but dozens of persistent windows, all titled C:\WINDOWS\system32\cmd.exe, stacked on the desktop.

That makes this much easier to reproduce/observe than the transient case, and easy to distinguish from the background-service windows:

  1. Windows 11, pwsh 7, default terminal (Windows Terminal).
  2. Configure 2–4 local npx-based stdio MCP servers.
  3. Start OpenCode Desktop 2.0.11, use it normally for a while.
  4. Get-CimInstance Win32_Process -Filter "Name='cmd.exe'" | Measure-Object → grows without bound; each entry's parent is the serve --service process or an npx-cli.js node.

For what it's worth regarding "plugins": my setup has plugins enabled, but none of them spawn anything — the parents are unambiguously the serve process and npx-cli.js.

Suggestion: mention in the description that the MCP spawn path (and descendant reaping — see #46253 and #47727) needs the same windowsHide + teardown treatment, otherwise the issue stays open after this lands.

@Nasnl

Nasnl commented Sep 25, 2026

Copy link
Copy Markdown

I have exactly the same thing: windows flashing very quickly several times per hour, sometimes even within 1 minute. According to logs, this happens in groups of 5 windows and it is indeed related to the MCP's you're connected with: as OpenCode says: OpenCode's background service spawns every local MCP server via cmd.exe /d /s /c ... (confirmed in the live process tree). Each spawn briefly allocates a visible console window. Your global config made codegraph, lystbot, lemma local servers, plus their npx/shim chains — so one location refresh ≈ 5+ cmd.exe flashes.

It can be solved by removing the MCP's, but well, that's not the idea. It started for me when I updated to the v2 desktop version. Never had it before.

@ramagdev

Copy link
Copy Markdown

Repro confirmed on opencode v2.0.18 (Windows 11, TUI + Desktop 2.0.18, managed background service).

Spawns in opencode.log, all role=server:

  • ~267x C:\Program Files\Git\cmd\git.EXE (snapshot loop: diff-files, ls-files, write-tree) even while idle
  • ~26x C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.EXE (one per shell tool call)

Sanitized sample:

timestamp=... message="spawning process" command="C:\Program Files\Git\cmd\git.EXE" args="["diff-files",...]" cwd="" role=server
timestamp=... message="spawning process" command="C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.EXE" args="["-NoLogo","-NoProfile",...]" cwd="" role=server

Process layout when it flashes:

opencode.exe serve --service   (detached, parent dead)
opencode.exe                   (TUI)

A/B on the same machine (spawns still logged in all three, only the visible window differs):

mode service process flash?
managed serve --service (detached) sibling of TUI, no console yes
opencode --standalone (serve --stdio, child of TUI) inherits TUI console no
serve --service run visibly in a Windows Terminal tab, TUI + Desktop attached to it has a real console no

How I tested the contender-side change: shallow-cloned branch v2, applied the one-line change to the service contender spawn, installed Bun 1.4.2 globally, ran bun install, then ran the dev CLI so it re-spawned its own service. Same spawns logged afterwards, visible windows persisted in my setup. The install was removed after the test.

Suggestion: both clean paths already exist in the product, so consider exposing one as a supported no-flash option — either document --standalone for single-client use, or surface a shared-service mode that runs attached to a visible console (what I do manually today: serve --service in a Windows Terminal tab, TUI + Desktop attached to the same service), so users don't have to choose between sharing one service and getting flashes.

@gabparrot

Copy link
Copy Markdown
Author

Tldr quick fix: add the hidden window flag to all hooks on your harnesses. For PowerShell this is -WindowStyle hidden

I ended up cancelling this and resolving it on my end. Here is the explanation for future readers as I see a lot of activity here still

In opencode v2 every terminal call is made in a new child process terminal window

Even if we merged this, it turns out it does not solve the problem entirely. I did make another pr to fix it completely but it turned out to be quite complex and felt like the wrong approach to the problem.

The windows are now properly hidden for native opencode2 commands. However any additional tools that you have like hooks for pre-commit or shell guards will not be hidden. You have to modify every tool in your harness to spawn a hidden window.

This biggest offender for me was the git rev-parse in a hook to check if we were in a git repo, and then enforce repo conventions if we are. This spawned a bunch of recursive windows.

@neriousy neriousy closed this Sep 26, 2026
@neriousy neriousy reopened this Sep 26, 2026
@neriousy

Copy link
Copy Markdown
Member

idk i can't reproduce it, i'll reopen for now and try to figure out exactly what's causing it

@neriousy neriousy closed this Sep 26, 2026
@PVLPM

PVLPM commented Sep 28, 2026

Copy link
Copy Markdown

@neriousy
Here is what I get at the start of each session and sometimes during subagent calls. The issue is still persistent in 2.0.18.
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants