Skip to content

fix(process): prevent Windows process-tree leaks after taskkill refusal#112202

Open
MohammedAlkindi wants to merge 1 commit into
openclaw:mainfrom
MohammedAlkindi:fix/windows-taskkill-force-escalation
Open

fix(process): prevent Windows process-tree leaks after taskkill refusal#112202
MohammedAlkindi wants to merge 1 commit into
openclaw:mainfrom
MohammedAlkindi:fix/windows-taskkill-force-escalation

Conversation

@MohammedAlkindi

Copy link
Copy Markdown

Closes #110789

What Problem This Solves

On Windows, OpenClaw could leak whole child process trees — npx-spawned MCP servers, sub-agents, and bash-tool subprocesses — after a session cancellation or shutdown. When the graceful taskkill /T (without /F) could not end a console process, cleanup relied solely on a delayed, unref'd force-kill timer; if the owning gateway process exited or restarted inside the grace window, that timer was canceled and the tree was never force-killed.

Why This Change Was Made

The Windows path remains graceful-first and retains the grace-period timer as a fallback, while escalating immediately when the graceful taskkill reports failure. It now wires the graceful exit code back to the caller:

  • Force escalation to taskkill /F /T fires immediately only when the graceful attempt reports failure and the target PID is still alive, so the leak is closed without waiting out the full window.
  • Dead / recycled-PID protection: taskkill returns the same non-zero exit for "needs /F" and "process not found," so a reported failure isn't proof the tree still exists. Both escalation paths skip the force-kill when the PID is no longer alive, and the decision is latched before that check. The liveness check prevents forced escalation when the PID is already dead at escalation time, reducing the risk of targeting a recycled PID.
  • Single-shot exit reporting: a failed spawn emits close after error with a negative errno; the runner reports only the first outcome, so a spawn failure is never misread as a non-zero verdict that escalates.

The Unix path and the explicit force: true / signalProcessTree(SIGKILL) paths are unchanged.

User Impact

Windows operators no longer accumulate orphaned MCP-server / sub-agent / shell process trees after cancellations and gateway restarts. Cleanup is more prompt (escalates as soon as the graceful attempt is refused) and safer (avoids forced escalation when the original PID is already gone).

Evidence

Windows 11, Node 24.18:

  • Focused unit tests src/process/kill-tree.test.ts: 26/26.
  • Consumer tests src/process/exec.windows.test.ts: 22/22; src/agents/mcp-stdio-transport.test.ts: 9/9.
  • Type checks (tsgo core + test-src), lint (oxlint), format (oxfmt --check): clean.
  • Real-Windows reproduction: a live process tree force-kills promptly via the reported-failure path; an already-dead PID receives no /F (and does receive one when the guard is removed).
  • trufflehog filesystem over both changed files: 0 secrets.

AI-assisted (Claude). An initial autoreview surfaced two findings — a missing liveness guard before escalation and escalation on a spawn-error close — were verified and fixed. The final automated rerun was blocked by a Windows-specific TruffleHog tooling issue, so secret scanning was completed separately (above).

@clawsweeper

clawsweeper Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper status: review started.

I am starting a fresh review of this pull request: fix(process): prevent Windows process-tree leaks after taskkill refusal This is item 1/1 in the current shard. Shard 0/1.

This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking.

Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: taskkill without /F leaks npx-spawned child processes (MCP servers, sub-agents)

1 participant