tailscale/tailscale · error

watcher error: %w

Error message

watcher error: %w

What it means

The fsnotify watcher delivered an error on its Errors channel and the watch loop propagates it via errCh and returns. fsnotify Errors carries low-level inotify failures, most commonly fsnotify.ErrEventOverflow (kernel event queue overflow) or errors from a watch that was removed because the watched directory was deleted or replaced.

Source

Thrown at cmd/containerboot/tailscaled.go:241

	}
	b, err := os.ReadFile(path)
	if err != nil {
		errCh <- fmt.Errorf("error reading configfile: %w", err)
		return
	}
	prevTailscaledCfg = b
	// kubelet mounts Secrets to Pods using a series of symlinks, one of
	// which is <mount-dir>/..data that Kubernetes recommends consumers to
	// use if they need to monitor changes
	// https://github.com/kubernetes/kubernetes/blob/v1.28.1/pkg/volume/util/atomic_writer.go#L39-L61
	const kubeletMountedCfg = "..data"
	toWatch := filepath.Join(tailscaledCfgDir, kubeletMountedCfg)
	for {
		select {
		case <-ctx.Done():
			return
		case err := <-errChan:
			errCh <- fmt.Errorf("watcher error: %w", err)
			return
		case <-tickChan:
		case event := <-eventChan:
			if event.Name != toWatch {
				continue
			}
		}
		b, err := os.ReadFile(path)
		if err != nil {
			errCh <- fmt.Errorf("error reading configfile: %w", err)
			return
		}
		// For some proxy types the mounted volume also contains tailscaled state and other files. We
		// don't want to reload config unnecessarily on unrelated changes to these files.
		if reflect.DeepEqual(b, prevTailscaledCfg) {
			continue
		}
		prevTailscaledCfg = b

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. Reduce unrelated file churn in the config directory (move state files elsewhere)
  2. Raise kernel limits: sysctl fs.inotify.max_queued_events=65536 and fs.inotify.max_user_watches
  3. Restart the container to re-establish the watch
  4. Check whether the directory itself is being replaced by your tooling; update files in place or recreate the watch

Example fix

$ sysctl -w fs.inotify.max_queued_events=65536
$ sysctl -w fs.inotify.max_user_watches=1048576
Defensive patterns

Strategy: retry

Validate before calling

// No pre-check reliably predicts inotify overflow; monitor instead:
// fs.file-max, fs.inotify.max_user_watches, fs.inotify.max_queued_events

Try / catch

// In your own watchers, degrade instead of dying on fsnotify errors:
if err := <-watcher.Errors; err != nil {
	if errors.Is(err, fsnotify.ErrEventOverflow) {
		// rescan state once and keep watching
	} else {
		// re-add watch / restart watcher
	}
}

Prevention

When it happens

Trigger: Very high churn of file events in the watched config directory overflowing fs.inotify.max_queued_events; the watched directory being deleted and recreated (breaking the watch); hitting per-user/system inotify instance or watch limits.

Common situations: Kubelet atomic-writer secret rotations combined with other busy files (tailscaled state) in the same directory; nodes with low fs.inotify.max_queued_events/max_user_watches; processes that rewrite the config dir instead of the files in it.

Related errors


AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15). Data as JSON: /api/errors/208dd34fc170f14c. Report an issue: GitHub.