[Linux] fileWatcher leaks 50k+ inotify watches after workspace close (VS Code upstream OK)

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

Cursor’s fileWatcher process recursively watches a large workspace and can consume ~50k+ inotify watches (nearly the entire Linux default budget). After closing the workspace (Cursor app still running), the watcher process stays alive and keeps the watches. This is a resource leak.
Raising fs.inotify.max_user_watches is not an acceptable primary fix — it just hides Cursor’s leak and wastes kernel memory.

Steps to Reproduce

Steps to reproduce

  1. On Linux, note current inotify usage (watches near zero / low hundreds is normal).
  2. Open a large folder workspace in Cursor (monorepo with tens of thousands of directories), e.g. a big source tree.
  3. Observe one utility process titled like:
    • fileWatcher [1:<workspaceStorageId>]
    • environ contains: VSCODE_ESM_ENTRYPOINT=vs/platform/files/node/watcher/watcherMain
  4. Inotify watches jump to ~50k+.
  5. Close the workspace / folder window, but do not quit Cursor (empty window is fine).
  6. Check again: the fileWatcher process is still alive and still holds ~50k+ watches.
  7. Only fully quitting Cursor, or manually killing that PID, releases the watches.

Control experiment (important)

Same machine, same large folder, VS Code 1.127.0:

  1. Open the folder → watches rise.
  2. Close the workspace, keep VS Code running.
  3. Watches drop immediately. No leak.
    So this appears Cursor-specific, not upstream VS Code.

Actual behavior

  • Workspace close does not dispose fileWatcher.
  • ~50k+ inotify watches remain held.
  • Exhausts default Linux inotify budget; other apps then hit ENOSPC / “No space left on device” on inotify.

Expected Behavior

  1. Closing a workspace must dispose its fileWatcher and drop all watches immediately.
  2. Provide a hard opt-out to disable native file watching entirely, e.g. files.watcherEnable: false / “never watch”.
  3. Do not tell Linux users to inflate fs.inotify.max_user_watches as the main fix.
  4. files.watcherExclude is not enough here — dispose path is broken, and default recursive watch of huge trees is too aggressive.

Operating System

Linux

Version Information

Version: 3.6.31
VS Code Extension API: 1.105.1
Commit: 81fcf2931d7687b4ff3f3017858d0c6dee7e2a60
Date: 2026-05-31T17:46:29.630Z
Layout: editor
Build Type: Stable
Release Track: Default
Electron: 39.8.1
Chromium: 142.0.7444.265
Node.js: 22.22.1
V8: 14.2.231.22-electron.0
xterm.js: 6.1.0-beta.220
OS: Linux x64 7.0.0-28-generic

Does this stop you from using Cursor

No - Cursor works, but with this issue

Environment

  • OS: Ubuntu 26.04 LTS (kernel 7.0.x)
  • Cursor:
  • Default fs.inotify.max_user_watches = 65536
  • Compared with: VS Code 1.127.0 on the same machine

Summary

Cursor’s fileWatcher process recursively watches a large workspace and can consume ~50k+ inotify watches (nearly the entire Linux default budget). After closing the workspace (Cursor app still running), the watcher process stays alive and keeps the watches. This is a resource leak.

Raising fs.inotify.max_user_watches is not an acceptable primary fix — it just hides Cursor’s leak and wastes kernel memory.

Steps to reproduce

  1. On Linux, note current inotify usage (watches near zero / low hundreds is normal).
  2. Open a large folder workspace in Cursor (monorepo with tens of thousands of directories), e.g. a big source tree.
  3. Observe one utility process titled like:
    • fileWatcher [1:<workspaceStorageId>]
    • environ contains: VSCODE_ESM_ENTRYPOINT=vs/platform/files/node/watcher/watcherMain
  4. Inotify watches jump to ~50k+.
  5. Close the workspace / folder window, but do not quit Cursor (empty window is fine).
  6. Check again: the fileWatcher process is still alive and still holds ~50k+ watches.
  7. Only fully quitting Cursor, or manually killing that PID, releases the watches.

Control experiment (important)

Same machine, same large folder, VS Code 1.127.0:

  1. Open the folder → watches rise.
  2. Close the workspace, keep VS Code running.
  3. Watches drop immediately. No leak.

So this appears Cursor-specific, not upstream VS Code.

Actual behavior

  • Workspace close does not dispose fileWatcher.
  • ~50k+ inotify watches remain held.
  • Exhausts default Linux inotify budget; other apps then hit ENOSPC / “No space left on device” on inotify.

Expected behavior

  1. Closing a workspace must dispose its fileWatcher and drop all watches immediately.
  2. Provide a hard opt-out to disable native file watching entirely, e.g. files.watcherEnable: false / “never watch”.
  3. Do not tell Linux users to inflate fs.inotify.max_user_watches as the main fix.
  4. files.watcherExclude is not enough here — dispose path is broken, and default recursive watch of huge trees is too aggressive.

How to identify the leaked process

# find fileWatcher PIDs
for p in /proc/[0-9]*; do
  grep -aq watcherMain "$p/environ" 2>/dev/null && {
    echo "pid=${p##*/}"
    tr '\0' '\n' < "$p/environ" | grep VSCODE_PROCESS_TITLE
  }
done

vscode is not happen

Hey @ZefengWang ,

I tried to reproduce this on Linux with a large folder (tens of thousands of directories) and, on my end, closing the folder disposes the fileWatcher and releases all its inotify watches within a few seconds. So I haven’t been able to confirm a leak yet, and I’d like to pin down what’s different in your setup.

One thing that’s easy to misread: when you close the folder, the window reloads into an empty window, and that empty window starts a new fileWatcher process. It only holds a handful of watches, but if you’re checking “is a fileWatcher still running?” you’ll see one - it just isn’t the old one. The thing to check is the watch count held by that PID, not whether a process exists.

Could you confirm a few things so I can dig in?

  1. The exact close action - File → Close Folder (or “Close Workspace”), closing the window with the X, or something else? And is this a single-folder or multi-root workspace?
  2. Right after closing, the watch count held by the surviving fileWatcher PID (your script is perfect for this - the number next to the PID, not just that it appears).
  3. Whether it still happens on the latest stable build (your version is from late May).

Separately, for the underlying inotify pressure itself: the practical lever today is files.watcherExclude for the heavy directories, then reload the window. That reliably takes the count from ~65k down to a few hundred:

"files.watcherExclude": {
  "**/node_modules/**": true,
  "**/target/**": true,
  "**/dist/**": true,
  "**/build/**": true,
  "**/.git/objects/**": true,
  "**/.cargo/**": true
}

Then run Developer: Reload Window. Reducing how broadly we watch large trees by default on Linux is something we’re already looking at. There isn’t a global “never watch” toggle today, but I’ve noted that request. If you can grab the post-close watch count above, I’ll take it from there.

  1. goto file close floder occurs leak
  2. after closing, the 50000+ inotify watches is belonged the pid of cursor. here is my code which curso AI offers.
python3 <<'PY'
import os,glob,collections
max_w=int(open('/proc/sys/fs/inotify/max_user_watches').read())
max_i=int(open('/proc/sys/fs/inotify/max_user_instances').read())
inst=collections.Counter(); watch=collections.Counter()
for fdpath in glob.glob('/proc/[0-9]*/fd/*'):
    try:
        if os.readlink(fdpath)!='anon_inode:inotify': continue
    except: continue
    pid=fdpath.split('/')[2]
    try:
        cmd=open(f'/proc/{pid}/cmdline','rb').read().replace(b'\0',b' ').decode('utf-8','replace')
        comm=open(f'/proc/{pid}/comm').read().strip()
    except: continue
    low=(cmd+' '+comm).lower()
    if 'cursorsandbox' in low: k='cursorsandbox'
    elif '/usr/share/cursor' in low or comm=='cursor': k='Cursor'
    elif 'xiezuo' in low: k='协作'
    elif 'wps' in low or 'kingsoft' in low: k='WPS'
    elif 'chrome' in low: k='Chrome'
    elif '/code' in low or comm=='code': k='VSCode'
    else: k=comm
    inst[k]+=1
    w=0
    try:
        for line in open(f'/proc/{pid}/fdinfo/{fdpath.split("/")[-1]}'):
            if line.startswith('inotify wd:'): w+=1
    except: pass
    watch[k]+=w
ti,tw=sum(inst.values()),sum(watch.values())
print(f'watches:    {tw} / {max_w}  ({100*tw/max_w:.1f}%)')
print(f'instances:  {ti} / {max_i}  ({100*ti/max_i:.1f}%)')
print(f'cursorsandbox: {os.popen("pidof cursorsandbox 2>/dev/null|wc -w").read().strip()}')
print('---')
for k,_ in watch.most_common(8):
    print(f'{k:<16} {inst[k]:>4} inst  {watch[k]:>6} watches')
PY

it shows below:

watches:    16444 / 65536  (25.1%)
instances:  78 / 128  (60.9%)
cursorsandbox: 0
---
Cursor             28 inst   16135 watches
xdg-desktop-por     3 inst      93 watches
gsd-xsettings       1 inst      43 watches
gvfsd-trash         2 inst      38 watches
update-notifier     1 inst      17 watches
snapd-desktop-i     1 inst      14 watches
gnome-shell         1 inst      13 watches
gjs                 1 inst      12 watches

after I go to File → Close Folder
it still shows

watches:    16446 / 65536  (25.1%)
instances:  79 / 128  (61.7%)
cursorsandbox: 0
---
Cursor             29 inst   16137 watches
xdg-desktop-por     3 inst      93 watches
gsd-xsettings       1 inst      43 watches
gvfsd-trash         2 inst      38 watches
update-notifier     1 inst      17 watches
snapd-desktop-i     1 inst      14 watches
gnome-shell         1 inst      13 watches
gjs                 1 inst      12 watches

  1. yes my version is from May, I can not try newist version now

yes I have tried this, so the file watcher become 16k+ rather 50k+

by the way, the ubuntu version is ubuntu 26.04, the wm is based wayland, X11 apps is based on Xwayland, I not sure if this effect this inotify leak

now I open a cursor blank edit window
it shows that has 65K+ watchers

watches:    65394 / 65536  (99.8%)
instances:  74 / 128  (57.8%)
cursorsandbox: 0
---
Cursor             24 inst   65085 watches
xdg-desktop-por     3 inst      93 watches
gsd-xsettings       1 inst      43 watches
gvfsd-trash         2 inst      38 watches
update-notifier     1 inst      17 watches
snapd-desktop-i     1 inst      14 watches
gnome-shell         1 inst      13 watches
gjs                 1 inst      12 watches

i notice the key path
first i open the agent window
it not run many watches
but when i open blank edit window
it can run 40k+ watches,sometimes it will hold 65k+ watches

after i close the edit window,
it not release the watches
after i close agent window, it will release the watches

in my opinion, when i close the edir window, cursor should release watches, rather than i close agent window

Here’s what’s going on. On the Agents Window, an edit window isn’t its own separate OS window; several of them live inside the one Agents Window. When you close just the edit window (File → Close Folder, or the editor view), that workspace’s file watcher isn’t disposed, so its inotify watches stay held. They’re only released when the whole Agents Window closes - which is exactly the difference you found between closing the edit window vs. the agent window. So this is a real dispose gap on the Agents Window on Linux, and I’ve reported it to our team.

That also fits your control test: regular VS Code opens one workspace per window, so closing a folder there disposes its watcher right away - it doesn’t have this multi-window-in-one model.

In the meantime, a few things:

  • To actually free the watches today, close the Agents Window (or fully quit Cursor), rather than only closing the edit window.
  • Keep your files.watcherExclude entries — that’s the right lever for how many watches each workspace holds (it’s why you went from ~50k to ~16k). Adding your repo’s other heavy generated/vendor directories will bring it down further.
  • If other tools hit ENOSPC in the meantime, raising fs.inotify.max_user_watches works as a temporary buffer only — I agree it’s a stopgap, not the fix.

On Wayland/Xwayland: that isn’t a factor here. inotify is handled in the kernel regardless of the display server, so the watch behavior is the same either way.

On a hard “never watch” switch: there isn’t a global toggle today (only the exclude/include lists), but I’ve noted the request.

No need to move to the latest build to help - you’ve already given us what we needed. I’ll update this thread when the fix lands.

ok,thank you
this is a heavy bug, in X11 env, when inotify runs out, some applications will not work
expect your good news about this problem
when it works i will try the new version