gitbutlerapp/gitbutler · warning

Watcher callback failed

Error message

Watcher callback failed

What it means

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.

Source

Thrown at apps/lite/electron/src/watcher.ts:207

	 * This function is responsible for starting the watcher and ensuring that there's ever only one watcher per project.
	 */
	private async ensureProjectWatcher(projectId: string): Promise<ProjectWatcherState> {
		// There are previous subscriptions to the project and a watcher is already running.
		const existing = this.projectWatchers.get(projectId);
		if (existing) return existing;

		// There is already a watcher being started by a previous subscriber.
		//
		// This is needed for the case in which two subscribers want
		// to subscribe to the same project at the same time (or very close to each other).
		const pending = this.pendingProjectWatchers.get(projectId);
		if (pending) return pending;

		// Create a watcher.
		const creation = watcherStart(projectId, (err, event) => {
			if (err) {
				// oxlint-disable-next-line no-console
				console.warn("Watcher callback failed", err);
				return;
			}
			this.forwardWatcherEvent(projectId, event);
		})
			.then((handle) => {
				const watcherState: ProjectWatcherState = {
					handle,
					subscriptionIds: new Set(),
				};
				// Once the watcher has been started, store the handle in the state.
				this.projectWatchers.set(projectId, watcherState);
				return watcherState;
			})
			.finally(() => {
				// Once the creation has been fulfilled, remove it from the pending map.
				this.pendingProjectWatchers.delete(projectId);
			});

View on GitHub (pinned to caf1f223d3)

Solutions

  1. On Linux, raise watch limits: sudo sysctl fs.inotify.max_user_watches=524288 (persist in /etc/sysctl.conf)
  2. If the project folder was moved or deleted, unsubscribe and re-subscribe with the corrected path
  3. Inspect the logged err payload to identify which notify backend failed
  4. After this warn, trigger a full project-state refetch so dropped events self-heal
Defensive patterns

Strategy: fallback

Validate before calling

function isWatchableDir(path: string): boolean {
	return !path.startsWith("\\\\") && !/^(?:/proc|/sys|/dev)//.test(path);
}

Type guard

interface WatcherError {
	code: string;
	message: string;
}
function isWatcherErrorPayload(x: unknown): x is WatcherError {
	return typeof x === "object" && x !== null && "code" in x;
}

Try / catch

watcherStart(projectId, (err, event) => {
	if (isWatcherErrorPayload(err)) {
		console.warn("Watcher callback failed", err);
		scheduleFullRefetch(projectId); // recover from dropped events
		return;
	}
	this.forwardWatcherEvent(projectId, event);
});

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/45af3aa5174b150b. Report an issue: GitHub.