# @cursor/sdk (local runtime) on Windows: background shells are only partially killed at the end of a run, so grandchild processes are orphaned and keep running

**URL:** <https://forum.cursor.com/t/cursor-sdk-local-runtime-on-windows-background-shells-are-only-partially-killed-at-the-end-of-a-run-so-grandchild-processes-are-orphaned-and-keep-running/176867>\
**Category:** Bug Reports\
**Tags:** terminal, cursor-sdk, windows\
**Created:** [October 31, 2026, 9:40am UTC](https://forum.cursor.com/t/cursor-sdk-local-runtime-on-windows-background-shells-are-only-partially-killed-at-the-end-of-a-run-so-grandchild-processes-are-orphaned-and-keep-running/176867 "2026-10-31T09:40:15Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![Arron\_Zhou](https://avatars.discourse-cdn.com/v4/letter/a/77aa72/32.png) [@Arron\_Zhou](https://forum.cursor.com/u/Arron_Zhou)\
**Post date:** [October 31, 2026, 9:40am UTC](https://forum.cursor.com/t/cursor-sdk-local-runtime-on-windows-background-shells-are-only-partially-killed-at-the-end-of-a-run-so-grandchild-processes-are-orphaned-and-keep-running/176867/1 "2026-10-31T09:40:16Z")

</div>

### Where does the bug appear (feature/product)?

Cursor SDK

### Describe the Bug

nvironment

@cursor/sdk 1.0.31, local runtime (Agent with local: {…}), embedded in an Electron 44 app (main process)  
Windows , x64  
The same code on macOS/Linux behaves correctly  
Summary At the end of each run, the SDK disposes the run’s session runtime, and that aborts all background shells. On POSIX the abort kills the whole process group (process.kill(-pid, …)). On Windows only the top-level shell process is terminated (ChildProcess.kill(), which is TerminateProcess). Nothing kills the process tree, so any process started by the shell survives as an orphan. It keeps running and can keep modifying the workspace after the turn has finished and the UI shows the agent as idle. Pressing Stop (run.cancel()) doesn’t help either: the abort path is the same.

Steps to reproduce

Create a local agent with cwd set to any folder, and send a prompt asking it to start this command in the background: node -e “setInterval(()=\>require(‘fs’).appendFileSync(‘stop-test.txt’, Date.now()+‘\n’), 500)”  
Let the run finish on its own. Don’t cancel it.  
Watch stop-test.txt, and run: Get-CimInstance Win32\_Process -Filter “Name=‘node.exe’” | ? CommandLine -like ‘_stop-test_’ | select ProcessId, ParentProcessId, CreationDate

### Steps to Reproduce

Create a local agent with cwd set to any folder, and send a prompt asking it to start this command in the background: node -e “setInterval(()=\>require(‘fs’).appendFileSync(‘stop-test.txt’, Date.now()+‘\n’), 500)”  
Let the run finish on its own. Don’t cancel it.  
Watch stop-test.txt, and run: Get-CimInstance Win32\_Process -Filter “Name=‘node.exe’” | ? CommandLine -like ‘_stop-test_’ | select ProcessId, ParentProcessId, CreationDate

### Expected Behavior

When the run ends, the background shell and all its descendants are terminated, which is the current macOS/Linux behaviour. The file stops growing.

The terminal record is marked aborted when the run ends, but node.exe keeps running and stop-test.txt keeps growing until the process is killed by hand. The ParentProcessId of node.exe is the dead shell’s pid from the terminal record.

Terminal record ~/.cursor/projects//terminals/451227.txt:

* * *

## pid: 20296 cwd: “E:\code\WorkBench” command: “node -e "setInterval(…)"” title: “Start interval Node process writing stop-test.txt” status: aborted started\_at: 2026-10-31T09:19:10.291Z running\_for\_ms: 14114

* * *

## exit\_code: unknown elapsed\_ms: 14114 ended\_at: 2026-10-31T09:19:24.405Z

The run ended at 09:19:24Z. node.exe was still writing several minutes later.

### Operating System

Windows 10/11

### Version Information

CURSOR SDK 1.0.31

### Additional Information

Code paths (from dist/bundled/index.js in 1.0.31)

When a run’s done settles, it calls await s.dispose() on the per-run session runtime. That reaches backgroundShellExecutor.dispose() → backgroundShellManager.dispose(), which aborts every running background shell.  
The shell spawn helper (AJ(…), spawned with detached: true) handles abort like this: if (X.detached) try { process.kill(-Q.pid, “SIGTERM”) } catch { Q.kill(“SIGTERM”) }, followed by SIGKILL after 1 s. On Windows, a negative pid isn’t a process group, so this falls back to killing the single process.  
The foreground executor’s abort and timeout handlers check process.platform !== “win32” explicitly and use E.kill(…) on Windows. That also kills a single process only.  
The bundle has no taskkill /T call and no Job Object usage.  
Suggested fix On Windows, terminate the whole tree: either taskkill /PID /T /F, or spawn shells into a Job Object with JOB\_OBJECT\_LIMIT\_KILL\_ON\_JOB\_CLOSE. The Codex CLI does the latter. Apply it to both the abort/timeout path and the background-shell dispose path.

Impact The agent appears stopped, but commands it started (code generators, formatters, watchers, scripts) keep running and editing files. Users see changes appear after the turn has ended or after they pressed Stop.

### Does this stop you from using Cursor

No - Cursor works, but with this issue

---

<div class="post-metadata">

**Author:** ![Colin](https://sea3.discourse-cdn.com/cursor1/user_avatar/forum.cursor.com/colin/32/90452_2.png) [@Colin](https://forum.cursor.com/u/Colin)\
**Post date:** [October 31, 2026, 6:17pm UTC](https://forum.cursor.com/t/cursor-sdk-local-runtime-on-windows-background-shells-are-only-partially-killed-at-the-end-of-a-run-so-grandchild-processes-are-orphaned-and-keep-running/176867/4 "2026-10-31T18:17:51Z")

</div>

Hi @Arron_Zhou!

This should have been fixed in _v1.0.32_. The latest version is _v1.2.0_. Could you try upgrading and see if the issue persists?
