# Idle Cursor burns 100% CPU per process: Chromium IO thread spins (~1.5M syscalls/s), and SSH reconnect awaits resolveAuthority forever

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

Environment

  • Cursor 3.20.21 (commit f09fca384ceca23f7bf21f9c23655b162641d740, VS Code 1.128.0)
  • Electron 42.10.0 / Chrome 148.0.7778.280
  • macOS 26.5.2 (25F84), Apple M4 (Mac16,10)
  • anysphere.remote-ssh 1.1.16
  • 6 windows: 4 Remote-SSH (two hosts), 2 local. Cursor had been running for ~2.5 days.

Summary

With no user activity, three Cursor processes each used one full core for many hours:

  • the main process (Cursor)
  • the renderer of a Remote-SSH window
  • the renderer of a local window (no remote involved)

In every case the hot thread is the Chromium IO thread (Chrome_IOThread in main, Chrome_ChildIOThread in renderers). It is not processing messages; it is spinning on kevent64 / mach_msg without blocking. The other renderers stay at ~0.7%.

Separately, a Remote-SSH window stayed in “reconnecting” for 21 hours after the network came back, because the reconnect path awaits resolveAuthority with no timeout.

On a laptop on battery this drains the battery while the machine looks idle.

Issue 1: IO thread busy loop

Measurements (2-second deltas, top -c d -stats pid,cpu,csw,msgsent,msgrecv,sysbsd,sysmach)

Process %CPU BSD syscalls / 2 s Mach syscalls / 2 s Mach msgs sent Mach msgs received
Renderer, local window 102.0 3,190,150 3,189,588 1,099 637
Renderer, SSH window 100.8 counter saturated at 2,147,483,647 saturated 834 421
Main process 100.5 2,910,973 2,911,021 58 11
Renderer, SSH window (healthy) 0.8 2,047 1,699 844 423
Renderer, local window (healthy) 0.7 2,007 1,619 810 404

The spinning processes exchange the same number of Mach messages as healthy ones, yet make ~1,500x more syscalls, in a 1:1 BSD:Mach ratio. That is kevent64 returning immediately followed by a mach_msg that receives nothing, about 1.5 million times per second.

Per-thread CPU (ps -M)

Exactly one thread per process is at ~99%, with system time exceeding user time:

  • main: Chrome_IOThread, STIME 613 min, UTIME 552 min
  • SSH renderer: Chrome_ChildIOThread, STIME 713 min, UTIME 409 min
  • local renderer: Chrome_ChildIOThread, STIME 573 min, UTIME 331 min

sample self-time of the hot thread

Chrome_ChildIOThread (SSH renderer, 857 samples)
  64.1%  mach_msg2_trap
  21.8%  kevent64
   3.7%  v8::MicrotasksScope::MicrotasksScope (symbolication approximate)
   1.3%  mach_absolute_time
   ...

An idle thread in the same process (Compositor) shows 100% in a single kevent64 frame, as expected. The spread across mach_msg2_trap, kevent64, mach_absolute_time and user code shows the IO thread is running, not blocked. The JS main threads are idle.

When it started

It is not reproducible on demand. Estimated from accumulated CPU time, spinning began on 2026-09-30 between roughly 15:00 and 20:00 JST, in all three processes. At 14:12 JST that day every Remote-SSH connection dropped at once (network outage), so the outage is a candidate trigger, but this is not confirmed. Once started, the spin never stops on its own; only restarting the process clears it.

Expected

The message pump should block in kevent64 when there is nothing to receive. If an event source stays signaled but yields no message, it should be removed or backed off, and ideally reported, rather than spun on indefinitely.

Issue 2: reconnect waits forever in resolveAuthority

Log (window15/renderer.log, Remote-SSH to www.riis.okayama-u.ac.jp)

2026-09-30 14:13:05.495 [info] Invoking resolveAuthority(ssh-remote)...
2026-09-30 14:13:05.495 [info] [LocalProcess0][resolveAuthority(ssh-remote,2)][0ms] obtaining proxy...
...
2026-10-01 11:04:00.907 [info] [remote-connection][Management   ][8457f…][reconnect] received socket timeout event (unacknowledgedMsgCount: 47924, timeSinceOldestUnacknowledgedMsg: 75075362, timeSinceLastReceivedSomeData: 75713154).
2026-10-01 11:04:10.876 [info] [LocalProcess0][resolveAuthority(ssh-remote,2)][75065355ms] waiting...
2026-10-01 11:06:49.637 [error] resolveAuthority(ssh-remote) returned an error after 75220739 ms Canceled
  • resolveAuthority was pending for 75,220,739 ms (20.9 h). It ended only when the window was closed.
  • The network had recovered long before; other windows to the same host were connected.
  • Unacknowledged protocol messages grew to 48,018 and were never dropped.
  • The “waiting…” line is logged every second per extension host, making up most of this 34,700-line log.

Another window (window11, kanri.chem.okayama-u.ac.jp) shows the same hang for 8,661,684 ms (2.4 h). When the pending call was canceled, it reconnected in 4.5 s:

2026-09-30 18:57:33.168 [error] resolveAuthority(ssh-remote) returned an error after 8661684 ms Canceled
2026-09-30 18:57:42.140 [info] resolveAuthority(ssh-remote) returned 'WebSocket(127.0.0.1:54764)' after 4478 ms

The request never reached Remote-SSH: the local extension hosts froze

The Remote-SSH output for that window pinged the server every minute and then stopped at 14:01:51. It logged nothing for the next 21 hours, including no “Received re-connection request”. The renderer reported both local extension hosts of that window as unresponsive and never as responsive again:

2026-09-30 14:12:45.437 [info] Extension host (LocalProcess [role: always-local] pid: 59299) is unresponsive.
2026-09-30 14:13:08.523 [info] Extension host (LocalProcess [role: user] pid: 59298) is unresponsive.

The same thing happened at the same moment in another Remote-SSH window (pids 60265 and 60257). The frozen hosts sit at 0% CPU, so they are blocked, not busy. Pids 59298 and 59299 were still alive 22 hours later, after their window had been closed (leaked processes).

So resolveAuthority was sent to an extension host that would never answer, and the workbench had no timeout to notice.

Cause in code (workbench.desktop.main.js, minified)

In _runReconnectingLoop, the authority is resolved before any timeout is armed:

const c = await vCh(this._options, this.reconnectionToken, this.protocol, t); // -> addressProvider.getAddress() -> resolveAuthority, no timeout
// ...
const u = setTimeout(() => l.cancel(), this._options.reconnectTimeoutMs ?? gsb); // 30 s, armed only after resolve returns

and the resolve itself:

c.cancelAndSet(() => s("waiting..."), 1e3); // only logs
const l = await a.resolveAuthority(e, t, n); // no timeout

Because this await never settles, none of the safeguards run: the 30 s reconnect timeout, the backoff schedule [0,5,5,10,...,120] s, and the 3 h grace period that turns into a permanent failure. remote.SSH.connectTimeout (30 s) does not help either, since the hang is above the extension’s connect logic.

Expected

  • Put a timeout (e.g. remote.SSH.connectTimeout or the 30 s reconnect timeout) on resolveAuthority during reconnection, cancel it on expiry, and fall through to the existing backoff and grace-period logic.
  • When a local extension host stays unresponsive (here, for 21 hours), restart it or tell the user, instead of waiting on it silently.
  • Terminate a window’s extension hosts when the window is closed.

Related reports (none with this signature)

  • [Memory/CPU leak in “Cursor Agents” window renderer after long-running sessions] : staff wrote on 2026-09-15 that “the sustained high CPU with no agent activity is still under investigation”. That thread is about the Agents window and JS-side leaks; here the windows are regular editor windows and the time is spent in the native IO thread, including in the main process.
  • [Cursor 3.19.13 maintains ~50% CPU at idle]: renderer plus GPU process, with the main process near 0%. Different profile.
  • [electron/electron#24385]: idle 100% CPU on macOS from a libuv clock bug, fixed in 2020. Same symptom, different code path.
  • microsoft/vscode-remote-release#8046, [#11648]: reconnect failures and requests for a reset without reloading, but not a resolveAuthority that never settles because the extension host froze.

(Links are removed by request)

Workaround

Quit Cursor completely (Cmd+Q) and relaunch. Reloading a window is not guaranteed to replace the renderer process.

Steps to Reproduce

During after-work duration. When I came back to the office, it fills 400% of CPU without doing anything.

Operating System

MacOS

Version Information

Cursor 3.20.21 (commit f09fca384ceca23f7bf21f9c23655b162641d740, VS Code 1.128.0)

Does this stop you from using Cursor

No - Cursor works, but with this issue

Hey, thanks for such a detailed write-up. The logs, measurements, and code analysis really help.

About the reconnect hang, your analysis matches what we’re seeing. During reconnect, the window waits for local extension hosts with no timeout, and a single stuck host can keep the window in Reconnecting forever. I’ve passed this to the team.

One important detail, your Mac is on 3.20.21. In 3.22 and newer, we added an auto-restart for a stuck local extension host after about a minute. This should reduce some of these hangs, but it doesn’t cover every case yet. To get the update, you need to fully quit Cursor with Cmd+Q and relaunch. Reloading the window isn’t enough.

For the CPU spin on the Chromium IO thread, we don’t have a solid repro yet. To narrow it down, a couple questions.

  1. Are any of the folders in those windows, or your home directory, on a network mount like NFS, SMB, or autofs?
  2. Next time the extension hosts hang, could you collect:
    • ps -o pid,ppid,stat,etime,command -p <pid1>,<pid2> for the stuck hosts
    • sample <pid> 5 for one of the stuck extension hosts
    • sample <pid> 5 for the main process that’s spinning

If you have a few minutes, here’s a short repro plan.

  1. Fully quit Cursor with Cmd+Q and relaunch.
  2. Open one Remote-SSH window and one local window, then wait for SSH to fully connect.
  3. Turn off Wi-Fi, or unplug Ethernet, for 2 minutes, then turn it back on.
  4. Wait 10 minutes without touching Cursor.
  5. Check if the SSH window reconnected, and look in Activity Monitor to see if a Cursor process is near 100% CPU.

Let me know what you see after step 5, and include your version from Cursor > About Cursor. Keep me posted on how it goes.

  1. Not always. In this case, three windows got 100 CPU% and one of the sessions was on a NFS folder in the remote.
  2. Thank you. After restarting Cursor, it is working well. will report when it occurs again.

And anyway I will update the Cursor to the newest one.

Hey, thanks for coming back with details. The NFS bit is really helpful. It matches what we suspected: when the local extension host gets stuck on a hung network file system, it can end up in a state it won’t recover from on its own, and then reconnect will wait forever. So this is a valuable signal.

Updating to the latest version is the right move. In 3.22 and newer we added an auto-restart for a hung local extension host after about a minute. That should cut down on some of these hangs, but it doesn’t cover every case yet. Also make sure you fully quit with Cmd+Q and restart. Reloading the window doesn’t always pick up the update.

For the CPU spin on the Chromium IO thread, we don’t have a repro yet. The most valuable thing is to capture process state before restarting. If it happens again, if you can, please collect this before Cmd+Q:

  • ps -o pid,ppid,stat,etime,command -p <pid1>,<pid2> for the stuck hosts
  • sample <pid> 5 for one of the stuck extension hosts
  • sample <pid> 5 for the main process that’s pegged at 100% CPU

We’re especially interested in the stat field for the stuck hosts. It’ll show whether they’re in uninterruptible wait, which would strongly point to an NFS hang.

Let me know if you hit it again. With those outputs, it’ll be much easier for us to dig in.

Hi Dean,

In the reported case, the CPU usage increased in the midnight after I left my office. Today, after your advices and Cursor update to 3.22, it is running pretty well. No increase of CPU usage occurs for 20 hours.

Thank you very much for your precise advices.