{"record":{"id":"45af3aa5174b150b","repo":"gitbutlerapp/gitbutler","slug":"watcher-callback-failed","errorCode":null,"errorMessage":"Watcher callback failed","messagePattern":"Watcher callback failed","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"apps/lite/electron/src/watcher.ts","lineNumber":207,"sourceCode":"\t * This function is responsible for starting the watcher and ensuring that there's ever only one watcher per project.\n\t */\n\tprivate async ensureProjectWatcher(projectId: string): Promise<ProjectWatcherState> {\n\t\t// There are previous subscriptions to the project and a watcher is already running.\n\t\tconst existing = this.projectWatchers.get(projectId);\n\t\tif (existing) return existing;\n\n\t\t// There is already a watcher being started by a previous subscriber.\n\t\t//\n\t\t// This is needed for the case in which two subscribers want\n\t\t// to subscribe to the same project at the same time (or very close to each other).\n\t\tconst pending = this.pendingProjectWatchers.get(projectId);\n\t\tif (pending) return pending;\n\n\t\t// Create a watcher.\n\t\tconst creation = watcherStart(projectId, (err, event) => {\n\t\t\tif (err) {\n\t\t\t\t// oxlint-disable-next-line no-console\n\t\t\t\tconsole.warn(\"Watcher callback failed\", err);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.forwardWatcherEvent(projectId, event);\n\t\t})\n\t\t\t.then((handle) => {\n\t\t\t\tconst watcherState: ProjectWatcherState = {\n\t\t\t\t\thandle,\n\t\t\t\t\tsubscriptionIds: new Set(),\n\t\t\t\t};\n\t\t\t\t// Once the watcher has been started, store the handle in the state.\n\t\t\t\tthis.projectWatchers.set(projectId, watcherState);\n\t\t\t\treturn watcherState;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\t// Once the creation has been fulfilled, remove it from the pending map.\n\t\t\t\tthis.pendingProjectWatchers.delete(projectId);\n\t\t\t});\n","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/apps/lite/electron/src/watcher.ts#L189-L225","documentation":"watcherStart(projectId, cb) starts a Rust file watcher and delivers (err, event) over IPC. When err is set, the callback warns and returns without forwarding — the watcher keeps running but that event is dropped. The error originates on the Rust side (notify or inotify backend) or in IPC delivery, not in this TypeScript.","triggerScenarios":"An active project watcher hits the Linux inotify limit (fs.inotify.max_user_watches exhausted), the watched directory is deleted or renamed underneath the watcher, or an event flood or serialization problem makes the backend deliver an error object instead of an event (apps/lite/electron/src/watcher.ts:207).","commonSituations":"Large workspaces or many concurrent watchers on Linux; the project folder moved or removed while subscribed; exotic filesystems such as network mounts producing backend errors.","solutions":["On Linux, raise watch limits: sudo sysctl fs.inotify.max_user_watches=524288 (persist in /etc/sysctl.conf)","If the project folder was moved or deleted, unsubscribe and re-subscribe with the corrected path","Inspect the logged err payload to identify which notify backend failed","After this warn, trigger a full project-state refetch so dropped events self-heal"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"function isWatchableDir(path: string): boolean {\n\treturn !path.startsWith(\"\\\\\\\\\") && !/^(?:/proc|/sys|/dev)//.test(path);\n}","typeGuard":"interface WatcherError {\n\tcode: string;\n\tmessage: string;\n}\nfunction isWatcherErrorPayload(x: unknown): x is WatcherError {\n\treturn typeof x === \"object\" && x !== null && \"code\" in x;\n}","tryCatchPattern":"watcherStart(projectId, (err, event) => {\n\tif (isWatcherErrorPayload(err)) {\n\t\tconsole.warn(\"Watcher callback failed\", err);\n\t\tscheduleFullRefetch(projectId); // recover from dropped events\n\t\treturn;\n\t}\n\tthis.forwardWatcherEvent(projectId, event);\n});","preventionTips":["Tune inotify limits on Linux dev machines and CI","Watch local paths only","Debounce subscribe/unsubscribe storms","Track warn frequency per project to catch pathological folders"],"tags":["typescript","electron","file-watcher","inotify","linux"],"backgroundTag":"file-watcher-error","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}