You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Installed/packaged (dist) builds hang forever on the "Restoring session..." loading screen. Running from source via npm start works fine.
Environment
macOS (Apple Silicon), packaged app launched from Finder/Dock
Account with a large number of Copilot sessions (repro seen with 4,972~/.copilot/session-state dirs)
Root cause
The session watchers use chokidar.watch(basePath, { usePolling: false, depth: 2 }).
On macOS chokidar only uses the efficient single FSEvents watcher when the native fsevents module is loadable.
fsevents is present in dev node_modules but is not bundled into the packaged app (the forge postPackage hook copies node-pty/better-sqlite3 but not fsevents).
Without it, chokidar with usePolling:false falls back to one fs.watch fd per directory. Watching thousands of session dirs exhausts the file-descriptor limit:
Error: EMFILE: too many open files, watch
at FSWatcher._handle.onchange (node:internal/fs/watchers:207:21)
The flood saturates the main process event loop, so the renderer's startup IPC stalls and isRestoring never clears → permanent "Restoring session...".
Finder/Dock-launched apps inherit a 256 fd soft limit (launchctl limit maxfiles), vs the high ulimit a terminal-launched npm start gets — which is why dev works and dist doesn't.
Reproduction
Have many ~/.copilot/session-state/<id> dirs (hundreds–thousands).
Package the app (npm run package) and launch the binary.
Main process floods EMFILE: too many open files, watch; UI stays on "Restoring session...".
Fix
Bundle fsevents in forge.config.tspostPackage (same mechanism as node-pty/better-sqlite3).
Externalize fsevents in vite.main.config.ts for a runtime probe.
Add canUseNativeRecursiveWatch() and make the copilot/claude-code watchers fall back to bounded polling when fsevents is unavailable (defense-in-depth: degrade instead of hang).
Summary
Installed/packaged (dist) builds hang forever on the "Restoring session..." loading screen. Running from source via
npm startworks fine.Environment
~/.copilot/session-statedirs)Root cause
chokidar.watch(basePath, { usePolling: false, depth: 2 }).fseventsmodule is loadable.fseventsis present in devnode_modulesbut is not bundled into the packaged app (the forgepostPackagehook copiesnode-pty/better-sqlite3but notfsevents).usePolling:falsefalls back to onefs.watchfd per directory. Watching thousands of session dirs exhausts the file-descriptor limit:isRestoringnever clears → permanent "Restoring session...".launchctl limit maxfiles), vs the highulimita terminal-launchednpm startgets — which is why dev works and dist doesn't.Reproduction
~/.copilot/session-state/<id>dirs (hundreds–thousands).npm run package) and launch the binary.EMFILE: too many open files, watch; UI stays on "Restoring session...".Fix
fseventsinforge.config.tspostPackage(same mechanism asnode-pty/better-sqlite3).fseventsinvite.main.config.tsfor a runtime probe.canUseNativeRecursiveWatch()and make the copilot/claude-code watchers fall back to bounded polling whenfseventsis unavailable (defense-in-depth: degrade instead of hang).