docker/compose · error

watcher.Add(%q): %w

Error message

watcher.Add(%q): %w

What it means

watchRecursively has a fast path: when the fsnotify watcher supports recursion (SetRecursive succeeded), one addWatch(dir) covers the whole tree; IsNotExist errors are tolerated (raced deletions) and everything else is wrapped as watcher.Add. The failure means the kernel refused the single recursive watch.

Source

Thrown at pkg/watch/watcher_naive.go:115

			err = d.addWatch(filepath.Dir(name))
			if err != nil {
				return fmt.Errorf("notify.Add(%q): %w", filepath.Dir(name), err)
			}
		}
	}

	go d.loop()

	return nil
}

func (d *naiveNotify) watchRecursively(dir string) error {
	if d.isWatcherRecursive {
		err := d.addWatch(dir)
		if err == nil || os.IsNotExist(err) {
			return nil
		}
		return fmt.Errorf("watcher.Add(%q): %w", dir, err)
	}

	return filepath.WalkDir(dir, d.walkAndAdd)
}

// walkAndAdd puts a watch on every directory of the tree being walked.
func (d *naiveNotify) walkAndAdd(path string, info fs.DirEntry, err error) error {
	if err != nil {
		// A directory we are not allowed to read is not a reason to abandon the
		// whole watch: we simply cannot see inside it, so skip it and carry on.
		if os.IsPermission(err) {
			logrus.Debugf("Not watching %s: %v", path, err)
			return filepath.SkipDir
		}
		return err
	}

	if !info.IsDir() {

View on GitHub (pinned to ddc4b044b6)

Solutions

  1. Check and raise instance limits: `sysctl fs.inotify.max_user_instances` then `sudo sysctl fs.inotify.max_user_instances=1024`.
  2. Close other watch sessions (VS Code remote, other compose watch runs) to free instances.
  3. If in a container, ensure the container has the capabilities/sysctls needed for inotify/fanotify, or run the watcher on the host.

Example fix

# before: default limits, many watchers
sysctl fs.inotify.max_user_instances   # e.g. 128
# after
sudo sysctl fs.inotify.max_user_instances=1024
Defensive patterns

Strategy: try-catch

Validate before calling

// Linux: check instance headroom before creating watchers
if runtime.GOOS == "linux" {
    b, _ := os.ReadFile("/proc/sys/fs/inotify/max_user_instances")
    // compare against open inotify fds: len(glob("/proc/self/fd/*")) heuristics
}

Try / catch

if err != nil {
    if errors.Is(err, syscall.EMFILE) {
        // actionable: suggest sysctl fs.inotify.max_user_instances=1024, do not retry
    }
    return err
}

Prevention

When it happens

Trigger: d.Add on a directory while running on a backend where isWatcherRecursive is true, and addWatch returns an error other than IsNotExist — e.g. inotify instance limit (EMFILE) on Linux recursive fsnotify, fanotify permission issues, or the directory disappearing concurrently with a non-NotExist errno.

Common situations: Hitting fs.inotify.max_user_instances because many compose/dev sessions each hold watchers; running inside containers where fanotify/inotify recursive support requires privileges the process lacks; kernel version differences changing which errors surface.

Related errors


AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15). Data as JSON: /api/errors/75471aaf9c28e967. Report an issue: GitHub.