Skip to content

cmk --watch causes EMFILE error on projects with many dependencies #321

@kyaido

Description

@kyaido

Description

When running cmk --watch on a real-world project with many npm dependencies, I get continuous EMFILE: too many open files errors.

This issue is more likely to occur in:

  • Projects with many npm packages installed (large node_modules)
  • Real production projects rather than minimal test setups

Environment:

  • OS: macOS (default file descriptor limit: 256)
  • Node.js: v22.x
  • @css-modules-kit/codegen: 0.8.1
  • Number of packages in node_modules: ~800+ (typical Next.js + Storybook project)

tsconfig.json:

{
  "include": [
    "**/*.module.css",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": [
    "**/node_modules/**"
  ]
}

Error:

Error: EMFILE: too many open files, watch
    at FSWatcher._handle.onchange (node:internal/fs/watchers:214:21)

Why This Matters

This issue may not be reproducible in minimal test projects because:

  • Small projects have few directories in node_modules
  • macOS default file descriptor limit (256) is only exceeded when watching many directories
  • The problem scales with the number of installed packages

In our project, node_modules contains approximately 2000+ files/directories, which quickly exhausts the file descriptor limit.

Root Cause Analysis

I debugged the issue and found the following:

  1. TypeScript's parseJsonSourceFileConfigFileContent returns wildcardDirectories containing the project root with recursive: true when include patterns like **/*.module.css are used.

  2. In runner.ts, these wildcardDirectories are passed directly to chokidar without considering tsconfig's exclude patterns.

  3. The ignored function in chokidar options returns false when stats is undefined (first call), which means all directories including node_modules are initially watched before being filtered.

  4. Chokidar creates fs.watch handles for every subdirectory before the ignored function can filter them out, exhausting file descriptor limits on projects with many dependencies.

Debug output showing the issue:

wildcardDirectories: [
  {
    "fileName": "/path/to/project",
    "recursive": true
  }
]

Expected Behavior

cmk --watch should respect tsconfig.json's exclude patterns when determining which directories to watch.
This would prevent watching node_modules and allow the tool to work on real-world projects without requiring users to increase system file descriptor limits.

Workaround

Changing tsconfig.json include patterns to use explicit directory prefixes works around the issue:

{
  "include": [
    "src/**/*.module.css",
    "src/**/*.ts"
  ]
}

However, this requires users to change their tsconfig.json structure, which may not always be desirable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions