Description
Running npx quartz build --serve crashes with EMFILE: too many open files when the content/ directory is a symlink to a folder outside quartz/.
The root cause is in quartz/cli/handlers.js — the globby call at line 471 uses **/*.ts, **/*.tsx, **/*.scss globs without restricting symlink traversal. When content/ is a symlink (e.g. content → ../vault), globby follows it, resolves back to the parent directory containing quartz/, and recurses infinitely:
quartz/content/quartz/quartz/content/quartz/quartz/content/quartz/...
This expands to 50,000+ paths, exhausting the OS file descriptor limit regardless of ulimit settings.
Note: The build itself (npx quartz build) succeeds without issues — only the --serve file watcher is affected.
Steps to Reproduce
-
Set up Quartz with a symlinked content directory:
ln -s /path/to/my/vault quartz/content
Where the vault is a sibling or ancestor-level directory of quartz/.
-
Run the dev server:
-
The site builds and serves successfully, but the file watcher immediately crashes.
Error Log
Parsed 247 Markdown files in 2s
Filtered out 245 files in 250μs
Emitting files
Emitted 393 files to `public` in 732ms
Done processing 247 files in 3s
Started a Quartz server listening at http://localhost:8080
hint: exit with ctrl+c
Error: EMFILE: too many open files, watch 'quartz/content/quartz/quartz/content/quartz/quartz/content/quartz/quartz/content/quartz/quartz/content/quartz/quartz/content/quartz/quartz/content/quartz/quartz/content/quartz/quartz/content/quartz/quartz/content/quartz/quartz/content/quartz/node_modules/mathjax-full/ts/output/common/Notation.ts'
at FSWatcher.<computed> (node:internal/fs/watchers:254:19)
at watch (node:fs:2551:36)
at createFsWatchInstance (file:///quartz/node_modules/chokidar/handler.js:126:16)
at setFsWatchListener (file:///quartz/node_modules/chokidar/handler.js:171:19)
at NodeFsHandler._watchWithNodeFs (file:///quartz/node_modules/chokidar/handler.js:327:22)
at NodeFsHandler._handleFile (file:///quartz/node_modules/chokidar/handler.js:393:29)
at NodeFsHandler._addToNodeFs (file:///quartz/node_modules/chokidar/handler.js:618:31)
at file:///quartz/node_modules/chokidar/index.js:367:25
at async Promise.all (index 50311)
fatal error: all goroutines are asleep - deadlock!
Suggested Fix
In quartz/cli/handlers.js, add followSymbolicLinks: false and ignore the content/ directory in the globby options, since the watcher only needs to watch source code and styles, not content files (content changes are already handled separately by the build pipeline):
const paths = await globby([
"**/*.ts",
"quartz/cli/*.js",
"quartz/static/**/*",
"**/*.tsx",
"**/*.scss",
"package.json",
- ])
+ ], { followSymbolicLinks: false, ignore: ["content/**"] })
Environment
- Quartz version: 4.5.2
- Node.js version: v24.13.0
- OS: macOS (Darwin 25.2.0)
- Content setup:
quartz/content symlinked to sibling vault/ directory
Description
Running
npx quartz build --servecrashes withEMFILE: too many open fileswhen thecontent/directory is a symlink to a folder outsidequartz/.The root cause is in
quartz/cli/handlers.js— theglobbycall at line 471 uses**/*.ts,**/*.tsx,**/*.scssglobs without restricting symlink traversal. Whencontent/is a symlink (e.g.content → ../vault), globby follows it, resolves back to the parent directory containingquartz/, and recurses infinitely:This expands to 50,000+ paths, exhausting the OS file descriptor limit regardless of
ulimitsettings.Note: The build itself (
npx quartz build) succeeds without issues — only the--servefile watcher is affected.Steps to Reproduce
Set up Quartz with a symlinked content directory:
Where the vault is a sibling or ancestor-level directory of
quartz/.Run the dev server:
The site builds and serves successfully, but the file watcher immediately crashes.
Error Log
Suggested Fix
In
quartz/cli/handlers.js, addfollowSymbolicLinks: falseand ignore thecontent/directory in the globby options, since the watcher only needs to watch source code and styles, not content files (content changes are already handled separately by the build pipeline):const paths = await globby([ "**/*.ts", "quartz/cli/*.js", "quartz/static/**/*", "**/*.tsx", "**/*.scss", "package.json", - ]) + ], { followSymbolicLinks: false, ignore: ["content/**"] })Environment
quartz/contentsymlinked to siblingvault/directory