[Windows][MCP] taskkill /T can terminate the host application after stale PPID reuse

Open 💬 0 comments Opened Jul 31, 2026 by icannotwait

What issue are you seeing?

Codex 0.145.0 forcibly terminated a long-running third-party Windows desktop host, its WebView processes, and other concurrent agents while tearing down a local stdio MCP server.

This is a process-ownership correctness issue, not only a process leak or taskkill.exe performance issue.

On Windows, LocalProcessTerminator stores only a numeric PID:

Cleanup then invokes taskkill /PID <pid> /T /F, suppresses stdout/stderr, and ignores the result:

taskkill /T reconstructs descendants from numeric PID/PPID relationships. A Windows parent PID is not a durable process identity: after the original parent exits, the child retains the numeric parent ID, and that number can later be reused by an unrelated process. A stale parent edge can therefore create a cycle in the numerical process graph. Tree cleanup from a descendant may then include the long-running host and all of its other children.

The live descendant chain immediately before this incident was reconstructed from Sysmon process-creation events:

DrawCode.exe 38700
  -> cmd.exe 47976
  -> node.exe 61580
  -> node.exe 37348
  -> codex.exe 48284
  -> codeg-mcp.exe 58508

The host had been started by a short-lived installer process and had remained alive for approximately 23 hours and 45 minutes. Its original process-creation event had already rotated out of the event log, so the exact stale PPID can no longer be recovered.

Observed timeline (UTC):

| Time | Event |
| --- | --- |
| 02:20:31.221 | Delegated Codex work completed normally. |
| 02:20:31.240 | codex.exe PID 48284 started taskkill /PID 58508 /T /F. |
| 02:20:31.269 | Multiple MCP/Codex helper processes began terminating. |
| 02:20:31.272 | Another Codex process started taskkill /PID 22304 /T /F. |
| 02:20:31.330 | Codex PID 48284 started taskkill /PID 38232 /T /F. |
| 02:20:31.362 | The host process, PID 38700, terminated with exit code 1. |

Approximately 90 processes terminated in the two-second incident window, including MCP companions, Codex processes, other agent processes, and WebView processes. Two taskkill.exe instances themselves terminated with 0xC000010A (STATUS_PROCESS_IS_TERMINATING). There was no matching Application Error, Windows Error Reporting event, crash dump, panic, fatal log, or normal host shutdown record.

Because three taskkill calls overlapped, the retained logs cannot identify which invocation issued the final TerminateProcess against the host, or whether the stale edge involved PID 58508, 22304, or 38232. The first broad termination cascade began immediately after the PID 58508 cleanup. This limitation does not change the unsafe primitive: /T is traversing recyclable numeric parent relationships without process-creation identity validation.

What steps can reproduce the bug?

The trigger is timing-dependent because it requires PID reuse:

  1. Start a long-running Windows host that launches Codex and local stdio MCP servers. Launch the host from a short-lived installer or helper process.
  2. Allow the original parent to exit while the host remains alive.
  3. Keep the host running under normal process churn until the stale parent PID is reused by an MCP server process or another process in its cleanup graph.
  4. Complete or disconnect an MCP-backed Codex session.
  5. Codex runs taskkill /PID <mcp-pid> /T /F.
  6. If the reused PID closes a numerical parent cycle, the host and its unrelated child trees are forcibly terminated.

A regression test does not need to wait for natural PID reuse. It can model a process snapshot containing a stale edge where the alleged child was created before the current process holding the parent PID. Cleanup must reject that edge, detect cycles, and never include the Codex process or its ancestors.

What is the expected behavior?

Terminating a local stdio MCP server must only affect the process tree that Codex created and owns. It must never terminate the Codex host, Codex itself, sibling agents, sibling MCP servers, or UI processes. PID reuse must not change cleanup ownership.

Additional information

Environment:

  • Windows 11 Pro, version 10.0.26200, x64
  • Codex CLI 0.145.0
  • Host: DrawCode 0.21.9-mycodebuddy.1, using codex-acp and a per-launch local stdio MCP companion

Suggested fix:

  1. Replace taskkill /T /F with a per-MCP Windows Job Object configured with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE.
  2. Assign the MCP root process to the Job before it can execute user code, using suspended creation or PROC_THREAD_ATTRIBUTE_JOB_LIST, then resume it.
  3. Retain Job/process handles as the ownership identity; keep PID only for diagnostics.
  4. Close MCP stdin and allow a bounded graceful-shutdown period, then call TerminateJobObject if necessary.
  5. If Job assignment fails, fall back to terminating only the already-owned direct child handle. Do not fall back to PID-tree traversal.

Codex already has a reusable Job Object abstraction:

Related reports:

Sanitized Sysmon event exports can be provided if needed. Local paths, command arguments containing authentication material, and private task content have been intentionally omitted.

View original on GitHub ↗