Cursor's auto-update hard-crashed my Mac 3 times in one evening — here's the forensic evidence

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

Cursor’s auto-update hard-crashed my Mac 3 times in one evening — here’s the forensic evidence

Cursor version: 2.6.20
Machine: MacBook Pro M3 Max, 36 GB RAM, macOS 26.3
Date: 2026-05-17


What happened

My Mac hard-crashed and rebooted itself three times in a single evening — at 19:15, 20:35, and 22:06. Not kernel panics. Worse: Apple Silicon watchdog timeouts — the system became so unresponsive that the hardware watchdog forced a reset.

Every single crash was caused by Cursor’s auto-update.

The evidence

Crash #1 — 19:15

macOS DiagnosticReports captured /usr/bin/ditto extracting a zip file. ditto was spawned by Cursor (Resource Coalition 1439 = com.todesktop.230313mzl4w4u92). The stack trace shows BOMCopierCopyWithOptions_BOMCopierCopyFromPKZip with 35+ levels of recursive _copyDir calls. It ran for 11 minutes and wrote 2.15 GB before the watchdog fired.

Crash #2 — 20:35

After the reboot, Cursor auto-started and resumed the update. The diagnostic report this time shows 177 threads all blocked in write() via libuv’s uv__fs_post — your Electron thread pool completely saturated. Simultaneously, the Squirrel.framework thread was in isVersionStandardremoveItem__removefile_tree_walker, recursively deleting the old version. 14.3 MB/s sustained writes over 150 seconds. Watchdog fired again.

Crash #3 — 22:06

After this reboot, Cursor started again. The main log at logs/20260517T204749/main.log tells the story:

20:47:49  updateURL .../cursor/2.6.20/...
22:04:36  update#setState checking for updates
22:04:37  UpdateService onUpdateAvailable()
22:04:37  update#setState downloading

Within 2 minutes of starting the download, the watchdog fired. ResetCounter confirms: Boot faults: wdog, reset_in_1 timeout.

The smoking gun

The ShipIt state file (ShipItState.plist) still points to an incomplete update:

updateBundleURL → "update.z1tErYz/Cursor.app/"
targetBundleURL → "/Applications/Cursor.app/"

And ShipIt_stderr.log shows the install was attempted twice the day before and never succeeded:

2026-05-16 19:24:28  Detected this as an install request
2026-05-16 23:01:03  Detected this as an install request

The update never completed. So after every watchdog reset, Cursor restarted, detected the incomplete update, and tried again. Crash. Reboot. Repeat.

Why this is unacceptable

  1. An IDE should never be able to take down the entire OS. No amount of disk I/O from a user-space application should cause a watchdog timeout on a 36 GB M3 Max machine. This is egregious behavior.

  2. The Squirrel update mechanism has no backoff or circuit breaker. It failed, crashed the machine, and then immediately tried the exact same thing again after reboot. Twice. There’s no “maybe we shouldn’t do that” logic anywhere in the pipeline.

  3. 177 concurrent file writes + recursive directory deletion + zip extraction all happening simultaneously means nobody thought about I/O contention. The update process apparently fires off work on every available thread without any concurrency control.

  4. This is a crash loop generator. By combining auto-start on boot with a failing update, you’ve created a mechanism that can render a machine unusable until someone knows how to SSH in or boot into safe mode to clean up the ShipIt state.

What you need to fix

  • Put a concurrency limit on libuv thread pool usage during updates. 177 simultaneous write() calls is absurd.
  • Add exponential backoff to update retries. If the last update attempt didn’t complete successfully, do not immediately retry on next launch.
  • Add a staged update mechanism. Download, extract, and install should not all happen on app launch while the main process is also doing heavy I/O.
  • If a watchdog or unexpected shutdown is detected on next boot, abort the pending update instead of retrying it.

How I stopped the bleeding

rm -rf ~/Library/Caches/com.todesktop.230313mzl4w4u92.ShipIt/update.*
rm -f ~/Library/Caches/com.todesktop.230313mzl4w4u92.ShipIt/ShipItState.plist
defaults write com.todesktop.230313mzl4w4u92 SUEnableAutomaticChecks -bool false

Disabled your auto-update entirely. I won’t be enabling it again until this is acknowledged and fixed.


All diagnostic reports, system logs, and stack traces are available. Happy to provide them to any Cursor engineer who wants to investigate.

Steps to Reproduce

Steps to Reproduce

  1. Install Cursor 2.6.20 on a Mac with auto-update enabled (default).
  2. Wait for Cursor’s Squirrel updater to detect a new version and download the update zip. The update is downloaded to ~/Library/Caches/com.todesktop.230313mzl4w4u92.ShipIt/update.<random>/.
  3. Squirrel spawns /usr/bin/ditto to extract the zip into the target location. This extraction is deeply recursive (the update package contains many nested directories).
  4. Simultaneously, Squirrel begins recursively deleting the old version via __removefile_tree_walker, while Cursor’s main process continues normal operation (extensions, language servers, file watchers — all generating their own disk I/O).
  5. The combined I/O load — ditto recursive extraction + Squirrel recursive deletion + 177 libuv threads in write() + normal workspace I/O — saturates the APFS I/O stack. The kernel becomes unresponsive.
  6. Apple Silicon hardware watchdog fires → forced system reset.
  7. After reboot, Cursor auto-starts (macOS window restoration). Squirrel detects the incomplete update from ShipItState.plist. Goes back to step 2.
  8. Repeat until someone manually cleans up the ShipIt state.

Operating System

MacOS

Version Information

Version: 2.6.20
Commit: b29eb4ee5f9f6d1cb2afbc09070198d3ea6ad760
Date: 2026-03-17T01:50:02.404Z
Browser: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Cursor/2.6.20 Chrome/142.0.7444.265 Electron/39.8.1 Safari/537.36

Does this stop you from using Cursor

Yes - Cursor is unusable

Hey, thanks for such a detailed write-up. The stack traces, ShipIt state, and timeline are way more useful than a generic update broke my Mac.

A few points that matter:

Version. 2.6.20 is from March 2026, and it comes before a bunch of fixes in the updater pipeline, including the libuv atexit deadlock around doQuitAndInstall and the shutdown watchdog. So this install got stuck in a window where known issues were not fixed yet, and the auto-update path is basically broken for it. If you try to update again through the same Squirrel or ShipIt flow, you’ll likely hit the same problem.

Recommendation. Install the latest version manually using the newest DMG from Cursor · Download, not via auto-update. Before that, it makes sense to keep the cleanup you already did, removing update.* and ShipItState.plist in ~/Library/Caches/com.todesktop.230313mzl4w4u92.ShipIt/. That’s correct. ShipIt was retrying in a loop because of that state.

About the bug. This is part of an already tracked family of issues around the updater and the libuv thread pool on macOS. It’s logged internally, but I can’t give an ETA for your specific case. Yours looks like I/O saturation up to the hardware watchdog level, not just a process hang. The items you listed, like a concurrency cap on libuv writes during update, backoff after a failed attempt, and abort on unexpected shutdown, all make sense. I’ll pass them along as extra context to the existing tickets.

If anything weird still happens with updates after a clean install of the current version, reply here and we’ll take a look.