fix(watchers): ignore .git/node_modules in apps watcher, degrade watcher fd errors

The apps-dir watcher recursed into anything an installed app ships;
ignore .git and node_modules segments like the workspace watcher does.
(The skills watcher already bounds itself with depth: 1.)

fs.watch failures (EMFILE/ENOSPC) escape chokidar's error handler as
uncaught exceptions from Node watcher internals and killed the main
process with the native error dialog. Catch them at the process level
and log instead — watching is degradable; other uncaught exceptions
keep the default dialog behavior.
This commit is contained in:
Gagan 2026-07-13 18:16:12 +05:30
parent 5bf743f93d
commit aa47871e47
2 changed files with 25 additions and 1 deletions

View file

@ -1,4 +1,4 @@
import { app, BrowserWindow, desktopCapturer, protocol, net, shell, session, safeStorage, type Session } from "electron";
import { app, BrowserWindow, desktopCapturer, dialog, protocol, net, shell, session, safeStorage, type Session } from "electron";
import path from "node:path";
import {
setupIpcHandlers,
@ -74,6 +74,24 @@ const APP_LAUNCHED_AT = Date.now();
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// fs.watch failures (EMFILE fd exhaustion, ENOSPC watch limits) surface as
// uncaught exceptions from Node's watcher internals, bypassing chokidar's
// 'error' handlers. Watching is a degradable feature — log and keep running.
// Everything else keeps Electron's default behavior (native error dialog),
// which we replicate since registering any listener suppresses it.
process.on('uncaughtException', (err) => {
const code = (err as NodeJS.ErrnoException | undefined)?.code;
if ((code === 'EMFILE' || code === 'ENOSPC') && (err?.stack ?? '').includes('FSWatcher')) {
console.error('[Main] file watcher error (non-fatal):', err);
return;
}
console.error('[Main] uncaught exception:', err);
dialog.showErrorBox(
'A JavaScript error occurred in the main process',
err?.stack ?? String(err),
);
});
// run this as early in the main process as possible
if (started) app.quit();

View file

@ -719,6 +719,12 @@ async function startWatcher(): Promise<void> {
if (watcher) return;
const w = chokidar.watch(APPS_DIR, {
ignoreInitial: true,
// Installed apps may ship .git/node_modules trees — thousands of files
// no consumer renders, at one watch fd per file (chokidar v4, no fsevents).
ignored: (watchedPath: string) => {
const segments = path.relative(APPS_DIR, watchedPath).split(path.sep);
return segments.includes('.git') || segments.includes('node_modules');
},
awaitWriteFinish: { stabilityThreshold: 180, pollInterval: 50 },
});
w.on('all', (eventName, absolutePath) => {