Bug Description
On Windows, killProcessTree sends taskkill /T /PID <n> (SIGTERM-equivalent, no /F) as the first attempt, then waits DEFAULT_GRACE_MS (3s) before force-killing with /F.
npx-spawned child processes (MCP servers like @modelcontextprotocol/server-filesystem, sub-agent runners, etc.) do not respond to taskkill without /F. If the gateway restarts within the 3-second grace window (e.g., watchdog auto-restart), the cleanup never reaches the /F path and zombie processes accumulate.
Root Cause
kill-tree-Cr15jS_s.js:
function signalProcessTreeWindows(pid, signal) {
runTaskkill(signal === "SIGKILL" ? [
"/F", // only SIGKILL has /F
"/T",
"/PID", String(pid)
] : [
"/T", // SIGTERM: no /F, npx processes ignore this
"/PID", String(pid)
])
}
Impact
Observed on a Windows 10 machine with 17 agents + MCP servers:
session disconnect
-> MCP disposeSession() -> taskkill /T (no /F) -> npx child ignores
-> gateway restarts before 3s grace -> cleanup interrupted
-> zombie node.exe x 20+ -> 2GB+ memory leak
-> OOM -> gateway killed -> watchdog restarts -> cycle repeats
This caused daily gateway crashes over a week (July 11-18), each time accumulating more zombies until the system hit memory limits.
Proposed Fix
Option A (recommended): Always force-kill on Windows. npx-spawned processes cannot do graceful shutdown on this platform.
function signalProcessTreeWindows(pid, signal) {
- runTaskkill(signal === "SIGKILL" ? [
- "/F",
- "/T",
- "/PID", String(pid)
- ] : [
- "/T",
- "/PID", String(pid)
- ]);
+ // Windows: always use /F because npx-spawned child
+ // processes don't respond to graceful taskkill.
+ runTaskkill(["/F", "/T", "/PID", String(pid)]);
}
Option B (more conservative): Increase DEFAULT_GRACE_MS from 3000 to 10000 to give npx processes more time, though this doesn't fix the underlying issue that they don't respond to non-force kill.
Environment
- OS: Windows 10.0.26200 (x64)
- Node: 24.16.0
- OpenClaw: 2026.7.1-2 (0790d9f)
Bug Description
On Windows,
killProcessTreesendstaskkill /T /PID <n>(SIGTERM-equivalent, no/F) as the first attempt, then waitsDEFAULT_GRACE_MS(3s) before force-killing with/F.npx-spawned child processes (MCP servers like
@modelcontextprotocol/server-filesystem, sub-agent runners, etc.) do not respond totaskkillwithout/F. If the gateway restarts within the 3-second grace window (e.g., watchdog auto-restart), the cleanup never reaches the/Fpath and zombie processes accumulate.Root Cause
kill-tree-Cr15jS_s.js:Impact
Observed on a Windows 10 machine with 17 agents + MCP servers:
This caused daily gateway crashes over a week (July 11-18), each time accumulating more zombies until the system hit memory limits.
Proposed Fix
Option A (recommended): Always force-kill on Windows. npx-spawned processes cannot do graceful shutdown on this platform.
function signalProcessTreeWindows(pid, signal) { - runTaskkill(signal === "SIGKILL" ? [ - "/F", - "/T", - "/PID", String(pid) - ] : [ - "/T", - "/PID", String(pid) - ]); + // Windows: always use /F because npx-spawned child + // processes don't respond to graceful taskkill. + runTaskkill(["/F", "/T", "/PID", String(pid)]); }Option B (more conservative): Increase
DEFAULT_GRACE_MSfrom3000to10000to give npx processes more time, though this doesn't fix the underlying issue that they don't respond to non-force kill.Environment