{"record":{"id":"b0361a63b23bc178","repo":"gitbutlerapp/gitbutler","slug":"failed-to-stop-project-watcher","errorCode":null,"errorMessage":"Failed to stop project watcher","messagePattern":"Failed to stop project watcher","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"apps/lite/electron/src/watcher.ts","lineNumber":133,"sourceCode":"\t\t// Remove this subscription from all the sender (window) subscriptions.\n\t\t// If this was the last subscription for this sender, we remove the sender from the map.\n\t\tconst senderIds = this.senderSubscriptions.get(subscription.senderId);\n\t\tif (senderIds) {\n\t\t\tsenderIds.delete(subscriptionId);\n\t\t\tif (senderIds.size === 0) this.senderSubscriptions.delete(subscription.senderId);\n\t\t}\n\n\t\t// Remove the project subscription.\n\t\t// If this was the last subscription to the project, the watcher is stopped.\n\t\tconst projectWatcher = this.projectWatchers.get(subscription.projectId);\n\t\tif (projectWatcher) {\n\t\t\tprojectWatcher.subscriptionIds.delete(subscriptionId);\n\t\t\tif (projectWatcher.subscriptionIds.size === 0) {\n\t\t\t\ttry {\n\t\t\t\t\tprojectWatcher.handle.stop();\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// oxlint-disable-next-line no-console\n\t\t\t\t\tconsole.warn(\"Failed to stop project watcher\", error);\n\t\t\t\t}\n\t\t\t\tthis.projectWatchers.delete(subscription.projectId);\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tprivate removeSenderSubscriptions(senderId: number): void {\n\t\tconst subscriptionIds = this.senderSubscriptions.get(senderId);\n\t\tif (!subscriptionIds) return;\n\n\t\tfor (const subscriptionId of subscriptionIds) this.removeSubscription(subscriptionId);\n\n\t\tthis.senderSubscriptions.delete(senderId);\n\t}\n\n\t/**","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/apps/lite/electron/src/watcher.ts#L115-L151","documentation":"unsubscribe() drops a subscription; when the last subscriber for a project leaves, it calls projectWatcher.handle.stop() to tell the Rust watcher to shut down. A throwing stop() (an IPC failure) is caught and warned, and the watcher is removed from the map regardless — so the manager forgets it even if the OS-level watcher survives, leaking until process exit.","triggerScenarios":"Unsubscribing the last subscriber for a project at apps/lite/electron/src/watcher.ts:133 while the stop() IPC fails: backend watcher thread already dead, renderer or IPC torn down mid-call, or the handle invalidated by an earlier error.","commonSituations":"Closing a Lite window that held the last subscription; rapid subscribe/unsubscribe churn; the Rust watcher having panicked earlier so later stops reject.","solutions":["Treat as benign when it occurs during shutdown; verify via backend logs that watchers actually stopped","If reproducible mid-session, capture the logged error payload and check the Rust watcher implementation for the matching failure","Restart the app to clear any leaked watchers","File an issue with the warn payload if it correlates with window-close races"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Only attempt the stop when this really is the last subscriber\nif (projectWatcher.subscriptionIds.size === 0) {\n\t/* safe to stop */\n}","typeGuard":"function isIpcGone(error: unknown): boolean {\n\treturn error instanceof Error && /destroyed|context bridge|closed/i.test(error.message);\n}","tryCatchPattern":"try {\n\tprojectWatcher.handle.stop();\n} catch (error) {\n\tconsole.warn(\"Failed to stop project watcher\", error);\n} finally {\n\tthis.projectWatchers.delete(subscription.projectId);\n}","preventionTips":["Unsubscribe on window close before destroying the webContents","Make the backend stop idempotent so double-stops are harmless","Periodically reconcile manager state with live backend watchers"],"tags":["typescript","electron","watcher","ipc","cleanup"],"backgroundTag":"watcher-cleanup-failed","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}