Summary
Desktop.get_state() → TreeService.get_state() walks the UI-Automation tree of the active window and every other top-level window (other_windows_handles). When one of those windows belongs to an Electron app whose main process is also hosting the MCP client (VS Code running Claude Code), the UIA traffic deadlocks that Electron main process. Windows then terminates it as hung (Event Log: Application Hang 1002 / WER AppHangB1 on Code.exe), which kills every session in the host — including the one that issued the Snapshot.
There is currently no way to keep specific processes/windows out of the walk (checked config.toml, all WINDOWS_MCP_* env vars, --tools/--exclude-tools).
Environment
- windows-mcp 0.8.5 via
uvx windows-mcp serve (stdio), Windows 10 Pro 19045
- Host: VS Code 1.133 + Claude Code extension; target app: Chrome (Google Cloud console — a large DOM)
- Three
Code.exe AppHang terminations in one afternoon, each with a Snapshot(use_dom=True) or WaitFor(use_dom=True) call in flight.
Minimal reproduction (isolated, no MCP client involved)
# walk.py — run with: uvx --from windows-mcp python walk.py <hwnd_of_a_VS_Code_window> 150
import os, sys, time
os.environ["WINDOWS_MCP_MAX_TREE_ELEMENTS"] = sys.argv[2]
from windows_mcp.desktop.service import Desktop
from windows_mcp import uia
d = Desktop(); hwnd = int(sys.argv[1])
node = uia.ControlFromHandle(hwnd)
t = time.perf_counter()
res = d.tree.get_nodes(hwnd, d.is_window_browser(node), 0, use_dom=False)
print("done", time.perf_counter() - t)
While it runs, a separate probe does SendMessageTimeout(hwnd, WM_NULL, ..., SMTO_ABORTIFHUNG, 500ms) every 250 ms and logs IsHungAppWindow(hwnd).
Result against a VS Code window (element budget only 150):
- probe:
ok=True ms=0 for ~5 s, then ok=False ms=~500 … then IsHungAppWindow=True
- the walk never returned (killed at 60 s)
- VS Code's main process stayed
Responding=False for >6 minutes after the walker process was killed — it does not recover; WER eventually terminates it.
Same test after relaunching VS Code with Chromium's --disable-renderer-accessibility: walk returns in 0.1 s with 3 nodes, probe stays ok=True throughout. So the trigger is UIA property/cache requests into Electron's renderer accessibility tree; the element budget does not prevent it because the deadlock happens before the budget is reached.
Impact
Any user running Windows-MCP from an Electron-hosted client (VS Code, Cursor, Claude Desktop, etc.) can lose their whole editor/session on an ordinary Snapshot when the walk reaches the host's own windows. It presents as "the app I was automating froze VS Code", so it is hard to attribute.
Requests
- Process/window exclusion list — e.g.
WINDOWS_MCP_EXCLUDE_PROCESSES=Code.exe,Cursor.exe (and/or [tree] exclude_processes in config.toml), applied in get_window_wise_nodes before get_nodes. Cheapest fix and lets users protect their host.
- Skip the MCP host's own process tree by default — the server can find its parent process chain; never walk windows that belong to it.
- Per-window UIA time budget in
get_nodes (abandon a window after N seconds) so one pathological window can't wedge the whole capture. Note the current 1800 s idle before the client aborts.
- README warning for Electron hosts pointing at
--disable-renderer-accessibility as the client-side mitigation.
Happy to test a build. Full write-up with timings available on request.
Summary
Desktop.get_state()→TreeService.get_state()walks the UI-Automation tree of the active window and every other top-level window (other_windows_handles). When one of those windows belongs to an Electron app whose main process is also hosting the MCP client (VS Code running Claude Code), the UIA traffic deadlocks that Electron main process. Windows then terminates it as hung (Event Log:Application Hang1002 / WERAppHangB1onCode.exe), which kills every session in the host — including the one that issued the Snapshot.There is currently no way to keep specific processes/windows out of the walk (checked
config.toml, allWINDOWS_MCP_*env vars,--tools/--exclude-tools).Environment
uvx windows-mcp serve(stdio), Windows 10 Pro 19045Code.exeAppHang terminations in one afternoon, each with aSnapshot(use_dom=True)orWaitFor(use_dom=True)call in flight.Minimal reproduction (isolated, no MCP client involved)
While it runs, a separate probe does
SendMessageTimeout(hwnd, WM_NULL, ..., SMTO_ABORTIFHUNG, 500ms)every 250 ms and logsIsHungAppWindow(hwnd).Result against a VS Code window (element budget only 150):
ok=True ms=0for ~5 s, thenok=False ms=~500… thenIsHungAppWindow=TrueResponding=Falsefor >6 minutes after the walker process was killed — it does not recover; WER eventually terminates it.Same test after relaunching VS Code with Chromium's
--disable-renderer-accessibility: walk returns in 0.1 s with 3 nodes, probe staysok=Truethroughout. So the trigger is UIA property/cache requests into Electron's renderer accessibility tree; the element budget does not prevent it because the deadlock happens before the budget is reached.Impact
Any user running Windows-MCP from an Electron-hosted client (VS Code, Cursor, Claude Desktop, etc.) can lose their whole editor/session on an ordinary Snapshot when the walk reaches the host's own windows. It presents as "the app I was automating froze VS Code", so it is hard to attribute.
Requests
WINDOWS_MCP_EXCLUDE_PROCESSES=Code.exe,Cursor.exe(and/or[tree] exclude_processesin config.toml), applied inget_window_wise_nodesbeforeget_nodes. Cheapest fix and lets users protect their host.get_nodes(abandon a window after N seconds) so one pathological window can't wedge the whole capture. Note the current 1800 s idle before the client aborts.--disable-renderer-accessibilityas the client-side mitigation.Happy to test a build. Full write-up with timings available on request.