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:
-
TypeScript's parseJsonSourceFileConfigFileContent returns wildcardDirectories containing the project root with recursive: true when include patterns like **/*.module.css are used.
-
In runner.ts, these wildcardDirectories are passed directly to chokidar without considering tsconfig's exclude patterns.
-
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.
-
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.
Description
When running
cmk --watchon a real-world project with many npm dependencies, I get continuousEMFILE: too many open fileserrors.This issue is more likely to occur in:
node_modules)Environment:
tsconfig.json:
{ "include": [ "**/*.module.css", "**/*.ts", "**/*.tsx" ], "exclude": [ "**/node_modules/**" ] }Error:
Why This Matters
This issue may not be reproducible in minimal test projects because:
node_modulesIn our project,
node_modulescontains approximately 2000+ files/directories, which quickly exhausts the file descriptor limit.Root Cause Analysis
I debugged the issue and found the following:
TypeScript's
parseJsonSourceFileConfigFileContentreturnswildcardDirectoriescontaining the project root withrecursive: truewhen include patterns like**/*.module.cssare used.In
runner.ts, thesewildcardDirectoriesare passed directly to chokidar without considering tsconfig'sexcludepatterns.The
ignoredfunction in chokidar options returnsfalsewhenstatsis undefined (first call), which means all directories includingnode_modulesare initially watched before being filtered.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:
Expected Behavior
cmk --watchshould respect tsconfig.json'sexcludepatterns when determining which directories to watch.This would prevent watching
node_modulesand 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.