{"record":{"id":"899867d1d515842b","repo":"gitbutlerapp/gitbutler","slug":"failed-to-stop-project-watcher-for-projectid","errorCode":null,"errorMessage":"Failed to stop project watcher for ${projectId}","messagePattern":"Failed to stop project watcher for (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"apps/lite/electron/src/watcher.ts","lineNumber":240,"sourceCode":"\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\n\t\t// While the watcher is being created, store it in the pending watchers,\n\t\t// so that we can avoid creating multiple handles for the same project.\n\t\tthis.pendingProjectWatchers.set(projectId, creation);\n\t\treturn creation;\n\t}\n\n\tstopAllWatchersForShutdown(): number {\n\t\tconst stopped = this.watcherSubscriptions.size;\n\n\t\tfor (const [projectId, projectWatcher] of this.projectWatchers) {\n\t\t\ttry {\n\t\t\t\tprojectWatcher.handle.stop();\n\t\t\t} catch (error) {\n\t\t\t\t// oxlint-disable-next-line no-console\n\t\t\t\tconsole.warn(`Failed to stop project watcher for ${projectId}`, error);\n\t\t\t}\n\t\t}\n\n\t\tthis.pendingProjectWatchers.clear();\n\t\tthis.projectWatchers.clear();\n\t\tthis.watcherSubscriptions.clear();\n\t\tthis.senderSubscriptions.clear();\n\n\t\treturn stopped;\n\t}\n\n\t/**\n\t * Setup a listener that removes all subscriptions for a given sender on it's destruction.\n\t *\n\t * Here we ensure that on the closing of a window, all subscriptions associated are closed.\n\t */\n\tprivate registerSenderCleanup(sender: Electron.WebContents): void {\n\t\tconst senderId = sender.id;","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/apps/lite/electron/src/watcher.ts#L222-L258","documentation":"stopAllWatchersForShutdown() iterates every project watcher at quit and calls handle.stop(); each failure is individually caught and warned with the projectId so one bad stop cannot block the rest. Maps are cleared unconditionally afterward, and the function returns how many subscriptions existed.","triggerScenarios":"Quitting the Lite app (destroy() leading to stopAllWatchersForShutdown at apps/lite/electron/src/watcher.ts:240) while any handle.stop() IPC fails — the backend already exited, Electron is tearing down IPC, or a watcher was invalidated by an earlier error.","commonSituations":"App quit with active project watchers; a crashed backend process; slow shutdown where the event loop drains before IPC replies arrive.","solutions":["Usually ignorable at quit — the process is about to exit anyway","Call destroy() early in the quit sequence, before windows are destroyed, so stop IPC still has a live channel","Ensure the backend terminates watchers on client disconnect as a safety net","Capture the payload and file an issue if it fires frequently"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"for (const [projectId, projectWatcher] of this.projectWatchers) {\n\ttry {\n\t\tprojectWatcher.handle.stop();\n\t} catch (error) {\n\t\tconsole.warn(`Failed to stop project watcher for ${projectId}`, error);\n\t}\n}","preventionTips":["Invoke watcher shutdown before destroying renderer windows","Give stop() a short timeout so quit is not blocked","Design backend watchers to self-terminate on client disconnect"],"tags":["typescript","electron","watcher","shutdown","ipc"],"backgroundTag":"watcher-cleanup-failed","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}