Skip to content

fix(plugin-ext): skip plugin watches rooted at an ancestor of the workspace#17633

Open
safisa wants to merge 4 commits into
eclipse-theia:masterfrom
safisa:safi-fix/skip-ancestor-of-workspace-watch
Open

fix(plugin-ext): skip plugin watches rooted at an ancestor of the workspace#17633
safisa wants to merge 4 commits into
eclipse-theia:masterfrom
safisa:safi-fix/skip-ancestor-of-workspace-watch

Conversation

@safisa

@safisa safisa commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

What it does

Fixes #17632

The backend ignores the recursive flag and always watches recursively, so a non-recursive watch rooted outside the workspace becomes a full recursive crawl. JDT-LS (redhat.java) watches the parent of the workspace folder (sent as recursive: false) only to detect its deletion — Theia then crawls every sibling of the workspace folder and can exhaust fs.inotify.max_user_watches; files.watcherExclude can't help since the root is outside the workspace.

MainFileSystemEventService.$watch now skips a non-recursive watch rooted at a strict ancestor of a workspace root; recursive requests and on/inside-workspace watches are unchanged. The root-cause alternative (a real non-recursive watcher, à la VS Code) is larger — both options are in #17632.

How to test

Linux, workspace alongside large siblings under a shared parent, with Red Hat Java (JDT-LS, non-LightWeight): via the diagnostic on #17630 or inotify-consumers, the parent's sibling trees are no longer watched. Covered by unit tests in main-file-system-event-service.spec.ts.

Follow-ups

Root-cause non-recursive watcher tracked in #17632; trade-off: loses workspace-folder-self-deletion detection for the skipped watchers.

Breaking changes

  • This PR introduces breaking changes and requires careful review. If yes, the breaking changes section in the changelog has been updated.

Review checklist

…kspace

Theia's backend ignores the `recursive` flag and always watches recursively, so
a non-recursive watch rooted at a strict ancestor of a workspace root - e.g. a
language server (`redhat.java` / JDT-LS) watching the PARENT of the workspace
folder via `RelativePattern(parentDir, folderName)` to detect deletion of the
folder itself - is turned into a recursive crawl of every sibling subtree, which
can exhaust the OS file-watch budget. `files.watcherExclude` cannot bound it
because the watch root is outside the workspace.

`MainFileSystemEventService.$watch` now skips such watches: no OS watch is
registered. Explicit recursive requests and watches on or inside a workspace
root are unchanged.

Closes eclipse-theia#17632
@github-project-automation github-project-automation Bot moved this to Waiting on reviewers in PR Backlog Jun 9, 2026

@colin-grant-work colin-grant-work left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an expedient fix, but I wonder whether we could, without too much difficulty, follow VSCode's example and add non-recursive watching as a feature, rather than dropping these watches?

Comment on lines +124 to +126
const isAncestorOfWorkspace = this.workspaceService.tryGetRoots().some(
root => uri.isEqualOrParent(root.resource) && !uri.isEqual(root.resource)
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a multi-root workspace where one root is a parent of another, a watch of the outer folder would fail here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 3cfc636. A folder that is itself a workspace root is now never skipped; the check only drops watches rooted strictly above every root. So in your nested multi-root example (/projects and /projects/my-app), a non-recursive watch on /projects is still registered. Added a regression test (still registers a non-recursive watch on an outer root that is itself the parent of another root).

safisa and others added 2 commits June 18, 2026 15:32
…f another root

In a multi-root workspace where one root is nested inside another (e.g. roots
`/projects` and `/projects/my-app`), `shouldSkipWatch` dropped a non-recursive
watch on the outer root: for the inner root the outer folder is a strict
ancestor, so the `.some(...)` check matched and the watch was skipped even
though the outer folder is itself a workspace root the user opened.

Treat a folder that equals any workspace root as never-skippable, and only drop
watches rooted strictly above every root. Adds a regression test for the nested
multi-root case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@safisa

safisa commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

Re: adding non-recursive watching as a first-class feature instead of dropping these watches — I looked into the feasibility.

The obstacle is the backend watcher: Theia's recursive watcher is @parcel/watcher, whose subscribe() only accepts { ignore, backend } and is always recursive — it has no non-recursive mode. The recursive flag is already carried from the frontend through the RPC layer, but it's dropped in DiskFileSystemProvider.watch (the backend filesystem-watcher-protocol WatchOptions only has ignored, no recursive).

So matching VSCode's approach would mean adding a separate fs.watch-based non-recursive watcher (VSCode routes non-recursive requests to its own NodeJSWatcher precisely because parcel can't do it), plus request routing and plumbing the flag end-to-end through the protocol — a sizable, separable change rather than a small tweak. My preference is to land this bounded mitigation now; first-class non-recursive watching could be pursued as a separate enhancement.

@safisa safisa requested a review from colin-grant-work June 18, 2026 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Waiting on reviewers

Development

Successfully merging this pull request may close these issues.

Watcher rooted at the parent of the workspace folder causes a recursive crawl of sibling trees (inotify exhaustion)

2 participants