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