Skip to content

dev-server on macOS misses changes outside the workspace root: one FSEvents stream per external directory exceeds the per-process limit #34246

Description

@peebeebee

Command

serve

Is this a regression?

  • Yes, this behavior used to work in the previous version

The previous version in which this bug was not present was

21.2.24 (watchpack)

Description

ParcelExternalManager.ensureDirWatched (packages/angular/build/src/utils/watcher.ts) calls parcelWatcher.subscribe() once per directory outside the workspace root. On macOS every subscription is its own FSEvents stream, and macOS refuses to start more than about 512 streams per process (parcel-bundler/watcher#190). The rejection is caught and ignored ("Ignore subscription errors for missing or restricted external directories"), so the files in those directories are silently not watched and editing them never triggers a rebuild.

Our setup: npm workspaces monorepo; the Angular workspace (angular.json) lives in <monorepo>/apps/<APP> and the app imports ~100 libraries from <monorepo>/libs/* through tsconfig paths. One ng serve registers 1,278 external directories (about 1,000 library source folders plus 274 node_modules packages). Instrumenting the catch in ensureDirWatched on 22.2.1 (which already contains #34187):

  • 1,278 subscribe attempts
  • 511 succeed
  • 767 fail with Error starting FSEvents stream

Which directories end up watched depends on subscription order. I edited 22 files in one batch with ng serve --verbose: 1 app file, 11 library files in directories whose subscription succeeded, and 10 in directories whose subscription failed. The 12 in watched directories show up in the modified list and rebuild; the 10 others never do (waited 150 s).

Minimal Reproduction

Any workspace whose app compiles sources from more than ~510 directories outside the workspace root, on macOS:

  1. Create a workspace in apps/app and ~600 small libraries in libs/lib-N/src/index.ts, mapped through tsconfig paths and all imported by the app.
  2. ng serve --verbose.
  3. Edit libs/lib-599/src/index.ts: no rebuild. Edit libs/lib-1/src/index.ts: rebuild (or the other way around, depending on subscription order).

The limit itself reproduces with @parcel/watcher alone (script in parcel-bundler/watcher#190: subscribing 1,000 sibling folders fails from about 500 on with Error starting FSEvents stream).

Exception or Error

Nothing is printed. The error is swallowed in ensureDirWatched; with an instrumented catch every failing subscription rejects with:

Error: Error starting FSEvents stream

Your Environment

@angular/build 22.2.1 (22.3.0-next.0 has the same ParcelExternalManager)
@angular/cli 22.2.1
@angular/core 22.2.1
@parcel/watcher 2.6.0
Node.js 24.21.0
npm 11.19
macOS 27.0 (26A428), Apple Silicon

Anything else relevant?

Workarounds measured on the same project with the same 22 edits; idle CPU is the dev-server process tree over 60 s:

Variant Streams Edits detected Idle CPU
22.2.1 as shipped 511 ok / 767 failed 12/22 0.1 %
NG_BUILD_WATCH_ROOT=1 511 ok / 767 failed 12/22 n/a
--poll 2500 chokidar 22/22 22.9 % of a core
--poll 1000 chokidar 22/22 46.9 % of a core
local patch: subscribe the common ancestor of the external directory and the workspace root 2 ok / 0 failed 22/22 0.1 %

Suggested fix: avoid one stream per directory on macOS, for example by subscribing a common ancestor (VS Code does the same, see the discussion in parcel-bundler/watcher#190), or by falling back to an ancestor subscription when subscribe rejects. At minimum, log the swallowed error in verbose mode so the problem is visible.

On 21.2.24 (watchpack) edits to these library files rebuilt reliably.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions