WatchDog UIA focus listener crashes the whole MCP server after long uptime (COM "Catastrophic failure", no Python traceback)
Summary
The local stdio server connects and runs fine, but after long uptime the UI-Automation WatchDog thread degrades: HandleFocusChangedEvent starts failing on every focus event with COMError (-2147220991, 'An event was unable to invoke any of the subscribers'), and eventually a single COMError (-2147418113, 'Catastrophic failure') (E_UNEXPECTED / 0x8000FFFF) takes down the entire process. Claude Desktop then shows the server as "Server disconnected."
This is not a startup, install, uv, or dependency problem. The server had connected cleanly ~17 hours earlier and answered initialize, tools/list, prompts/list, and resources/list. The crash is at runtime, in the watchdog.
Version / environment
- Windows-MCP: 0.7.1 (installed as the Claude Desktop extension
ant.dir.cursortouch.windows-mcp)
- Host: Claude Desktop (MSIX/Store-packaged build), transport
stdio, MODE=local
- OS: Windows 11 Pro (build 26200)
- Python: 3.13 (extension
.venv)
Evidence (from Claude Desktop's mcp-server-Windows-MCP.log, paths redacted)
Clean connect the night before:
2026-07-12T23:05:46Z [Windows-MCP] Message from server: id=0 result (initialize)
2026-07-12T23:05:46Z [Windows-MCP] method="notifications/initialized"
2026-07-12T23:05:46Z [Windows-MCP] tools/list / prompts/list / resources/list -> results
INFO:mcp.server.lowlevel.server:Processing request of type ListToolsRequest
Then ~17 hours of this, thousands of times:
DEBUG:windows_mcp.watchdog.event_handlers:Focus callback COM error: (-2147220991, 'An event was unable to invoke any of the subscribers', (None, None, None, 0, None))
...
DEBUG:windows_mcp.watchdog.event_handlers:Focus callback COM error: (-2147418113, 'Catastrophic failure', (None, None, None, 0, None))
Immediately followed by process death:
2026-07-13T16:29:29Z [Windows-MCP] Server transport closed unexpectedly, this is likely due to the process exiting early.
2026-07-13T16:29:29Z [Windows-MCP] [error] Server disconnected.
Total uptime before crash: ~17h24m.
Root cause analysis
WatchDog._run (src/windows_mcp/watchdog/service.py) drives an STA event loop and calls comtypes.client.PumpEvents(0.1) each iteration.
FocusChangedEventHandler.HandleFocusChangedEvent (src/windows_mcp/watchdog/event_handlers.py:19) catches comtypes.COMError and logs it, so the per-event -2147220991 failures are non-fatal and simply spam the log. The UIA event subscription is never re-established, so once degraded it stays degraded.
- The final
-2147418113 (E_UNEXPECTED) is not surfaced as a catchable Python exception: the outer except Exception: logger.error("WatchDogService died") in _run was never logged. The process just exits. This is consistent with a native COM callback invoked against a torn-down/dangling interface during PumpEvents (an access violation), which crashes the interpreter rather than raising.
- Because the watchdog runs in the same process as the FastMCP stdio server, its native crash takes the server down with it, producing the "Server disconnected" state.
Likely trigger for the pipeline degradation: a desktop/session state change during the long uptime (sleep/resume, lock/unlock, display or GPU reconfiguration). The subscription made via AddFocusChangedEventHandler does not survive that, and there is no recovery path.
Impact
- The single most useful long-running tool (desktop automation) silently dies after the machine has been up a while, and the user only discovers it when a call fails or they open Settings.
- There is currently no supported way to disable the watchdog:
WatchDog() is constructed unconditionally in src/windows_mcp/__main__.py:56, with no env var or manifest.json user_config toggle. So there is no user-side workaround short of restarting the extension.
Suggested fixes (in rough priority order)
- Isolate the watchdog crash from the server. A watchdog failure should never terminate the MCP server. The watchdog is an optimization (focus-tree cache); on failure, fall back to on-demand tree queries and keep serving tools.
- Recover the subscription instead of spiraling. Count consecutive
COMErrors in HandleFocusChangedEvent; past a threshold, RemoveFocusChangedEventHandler + re-Add... (ideally after re-creating the IUIAutomation client, since the existing pointer may be corrupt). This turns a permanent degradation into a self-healing reconnect.
- Add a disable switch. A
WINDOWS_MCP_WATCHDOG=off env var (wired into manifest.json user_config like the existing screenshot_backend/debug toggles) would give users on unstable UIA environments a supported opt-out.
- Re-initialize COM/UIA on
E_UNEXPECTED. Treat -2147418113 specifically: tear the STA thread down cleanly, re-CoInitialize, rebuild the client, and restart, rather than reusing state that just reported catastrophic failure.
- Make degradation observable before the crash. Rate-limit the per-event
COMError log (it currently emits thousands of identical lines) and emit a single WARN plus a heartbeat when the focus pipeline has been failing for N seconds.
Repro notes
Not deterministic on demand; it correlates with long uptime plus a session/display state change. Leaving local mode running overnight on a machine that sleeps/resumes reproduces the -2147220991 flood; the terminal -2147418113 and process exit follow once the COM state fully collapses.
WatchDog UIA focus listener crashes the whole MCP server after long uptime (COM "Catastrophic failure", no Python traceback)
Summary
The
localstdio server connects and runs fine, but after long uptime the UI-Automation WatchDog thread degrades:HandleFocusChangedEventstarts failing on every focus event withCOMError (-2147220991, 'An event was unable to invoke any of the subscribers'), and eventually a singleCOMError (-2147418113, 'Catastrophic failure')(E_UNEXPECTED / 0x8000FFFF) takes down the entire process. Claude Desktop then shows the server as "Server disconnected."This is not a startup, install,
uv, or dependency problem. The server had connected cleanly ~17 hours earlier and answeredinitialize,tools/list,prompts/list, andresources/list. The crash is at runtime, in the watchdog.Version / environment
ant.dir.cursortouch.windows-mcp)stdio,MODE=local.venv)Evidence (from Claude Desktop's
mcp-server-Windows-MCP.log, paths redacted)Clean connect the night before:
Then ~17 hours of this, thousands of times:
Immediately followed by process death:
Total uptime before crash: ~17h24m.
Root cause analysis
WatchDog._run(src/windows_mcp/watchdog/service.py) drives an STA event loop and callscomtypes.client.PumpEvents(0.1)each iteration.FocusChangedEventHandler.HandleFocusChangedEvent(src/windows_mcp/watchdog/event_handlers.py:19) catchescomtypes.COMErrorand logs it, so the per-event-2147220991failures are non-fatal and simply spam the log. The UIA event subscription is never re-established, so once degraded it stays degraded.-2147418113(E_UNEXPECTED) is not surfaced as a catchable Python exception: the outerexcept Exception: logger.error("WatchDogService died")in_runwas never logged. The process just exits. This is consistent with a native COM callback invoked against a torn-down/dangling interface duringPumpEvents(an access violation), which crashes the interpreter rather than raising.Likely trigger for the pipeline degradation: a desktop/session state change during the long uptime (sleep/resume, lock/unlock, display or GPU reconfiguration). The subscription made via
AddFocusChangedEventHandlerdoes not survive that, and there is no recovery path.Impact
WatchDog()is constructed unconditionally insrc/windows_mcp/__main__.py:56, with no env var ormanifest.jsonuser_configtoggle. So there is no user-side workaround short of restarting the extension.Suggested fixes (in rough priority order)
COMErrors inHandleFocusChangedEvent; past a threshold,RemoveFocusChangedEventHandler+ re-Add...(ideally after re-creating theIUIAutomationclient, since the existing pointer may be corrupt). This turns a permanent degradation into a self-healing reconnect.WINDOWS_MCP_WATCHDOG=offenv var (wired intomanifest.jsonuser_configlike the existingscreenshot_backend/debugtoggles) would give users on unstable UIA environments a supported opt-out.E_UNEXPECTED. Treat-2147418113specifically: tear the STA thread down cleanly, re-CoInitialize, rebuild the client, and restart, rather than reusing state that just reported catastrophic failure.COMErrorlog (it currently emits thousands of identical lines) and emit a single WARN plus a heartbeat when the focus pipeline has been failing for N seconds.Repro notes
Not deterministic on demand; it correlates with long uptime plus a session/display state change. Leaving
localmode running overnight on a machine that sleeps/resumes reproduces the-2147220991flood; the terminal-2147418113and process exit follow once the COM state fully collapses.