Same failure on macOS with Cursor 3.17.21, and I think I have the actual root cause. The log in post 5 has the exact signature.
Root cause: a rejecting dispose() aborts _deactivateAll, so the host never calls process.exit()
The relevant frames from that log:
at Object.executeCommand (extensionHostProcess.js:214:142358)
at set hasGitHubRepositories (app/extensions/github/dist/extension.js:2:270497)
...
at KHs._deactivate (extensionHostProcess.js:146:15497)
at Array.map ()
at KHs._deactivateAll (extensionHostProcess.js:146:12756)
at KHs.terminate (extensionHostProcess.js:146:13110)
... setContext undefined
_deactivateAll maps over every extension’s dispose() without awaiting or catching. By the time it runs, the renderer has already closed the MessagePort, so any executeCommand("setContext", ...) inside a dispose() rejects with Canceled. That rejection escapes the map, terminate() never completes, and process.exit() is never reached. The process stays alive with a dead libuv event loop.
Worth stressing: in that log the triggers are Cursor’s own bundled extensions/git and extensions/github, so this is not a third-party extension problem. On my machine I hit it with three different extensions: ms-vscode.test-adapter-converter, eamodio.gitlens, and ms-toolsai.datawrangler. Uninstalling them just promotes whoever is next in the map.
Why it pegs a core, and why SIGTERM is ignored
Once the event loop is dead, nothing drains libuv’s signal pipe. Every incoming signal runs uv__signal_handler, which locks (read on a pipe), writes the signal to the queue pipe, then unlocks (write). fs_usage -w -t 3 on the stuck process:
3,156,213 syscalls in 3 seconds
1,052,071 read F=13 B=0x1 <- uv__signal_lock
1,052,071 write F=28 [ 35 ] <- uv__signal_write -> EAGAIN, pipe FULL
1,052,071 write F=14 B=0x1 <- uv__signal_unlock
A perfect 1:1:1 triple, about 350,000 signal-handler invocations per second. The signal is never consumed, so it re-fires immediately.
That also explains the SIGTERM finding in the closed duplicate (topic 155821, which guessed the process was waiting for IPC): libuv catches SIGTERM, tries to queue it to the full pipe, gets EAGAIN, and it never reaches the dead loop. SIGKILL bypasses handlers, which is why only kill -9 works.
spindump on the main thread: 0.36 cycles/instruction, 53.6 G instructions in 5 s. A cache-hot syscall loop, not a stall and not memory thrashing.
Impact and reproduction
24 GB M-series, Cursor 3.17.21. One stranded host per closed window, each pinning a core and holding its full JS heap (4.2 GB in one case, unreclaimable). I got down to 57 MB free RAM with 22.8 GB of 24 GB swap used and about 14,100 compressor decompressions/sec. SIGKILLing the stranded hosts took load average from 8.1 to 2.7 and freed roughly 9 GB of swap.
Reproduction is trivial: open a window, let extensions finish activating, then close it. The (user) host stays at 100% CPU indefinitely, while its retrieval, always-local and agent-exec siblings all log “exiting with code 0” normally. Multi-root workspaces reproduced it most reliably for me: across 25 window logs, only the multi-root windows stranded.
Suggested fix
Any one of these three would break the failure mode independently.
Guard the map: use await Promise.allSettled(...) in _deactivateAll so a single rejection cannot abort the rest.
Bound the shutdown: after a few seconds, call process.exit() regardless of pending disposals. This alone makes the whole class of bug non-fatal.
Reap from the main process: kill (user) hosts whose window has closed and which have not exited within a grace period.
Still present in 3.17.21, about six months after the closed report on 2.6.21, and the log in post 5 shows it on Windows too, so it is not macOS-specific.