{"record":{"id":"0230b1c0133b80b5","repo":"abiosoft/colima","slug":"error-running-watcher-w","errorCode":null,"errorMessage":"error running watcher: %w","messagePattern":"error running watcher: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"daemon/process/inotify/events.go","lineNumber":73,"sourceCode":"\t\tcase vols := <-vols:\n\t\t\tif !volsChanged(vols) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tlog.Tracef(\"volumes changed from: %+v, to: %+v\", currentVols, vols)\n\n\t\t\tcurrentVols = vols\n\n\t\t\tif cancel := cancelWatch; cancel != nil {\n\t\t\t\t// delay a bit to avoid zero downtime\n\t\t\t\ttime.AfterFunc(time.Second*1, cancel)\n\t\t\t}\n\n\t\t\tctx, cancel := context.WithCancel(ctx)\n\t\t\tcancelWatch = cancel\n\n\t\t\tgo func(ctx context.Context, vols []string, mod chan<- modEvent) {\n\t\t\t\tif err := watcher.Watch(ctx, vols, mod); err != nil {\n\t\t\t\t\tlog.Error(fmt.Errorf(\"error running watcher: %w\", err))\n\t\t\t\t}\n\t\t\t}(ctx, vols, mod)\n\n\t\t// handle modification events\n\t\tcase ev := <-mod:\n\t\t\tnow := time.Now()\n\n\t\t\t// rate limit, handle at most 50 unique items every 500 ms\n\t\t\tif now.Sub(last) < time.Millisecond*500 {\n\t\t\t\tif _, ok := cache[ev.path]; ok {\n\t\t\t\t\tcontinue // handled, ignore\n\t\t\t\t}\n\t\t\t\tif len(cache) > 50 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlast = now\n\t\t\t\tcache = map[string]struct{}{} // >500ms, reset unique cache","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/abiosoft/colima/blob/c3a5f9184d83a197184f897a9f07eb3c01b3bc88/daemon/process/inotify/events.go#L55-L91","documentation":"Whenever the polled set of container volumes changes, events.go spawns a detached goroutine running watcher.Watch (after a 1s delayed cancel of the previous watcher). A failure inside that goroutine cannot return to the caller, so it is only logged: 'error running watcher: <cause>'. The observable effect is degraded file-change syncing until the next volume refresh spawns a replacement watcher — the daemon keeps running.","triggerScenarios":"notify.Watch fails to register a recursive watch for one of the volume dirs: the directory was deleted, permission denied, or an OS-level FSEvents/watch-resource error; the wrapped cause comes from Watch ('invalid directory' or \"error watching directory recursively '<dir>'\").","commonSituations":"containers with bind-mount sources removed while running; environments with very many watched directories hitting OS limits; paths inaccessible to the root daemon.","solutions":["read the wrapped cause on the same log line to identify the failing directory","remove/recreate the offending container or volume, then let the 5s volume poll re-spawn a watcher, or restart colima","if the cause is resource exhaustion, raise file-handle/watch limits (ulimit, launchctl) and restart"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// inside a custom dirWatcher wrapper\ngo func(ctx context.Context, vols []string, mod chan<- modEvent) {\n    if err := watcher.Watch(ctx, vols, mod); err != nil {\n        log.Error(fmt.Errorf(\"error running watcher: %w\", err))\n        // no rethrow: the next volume refresh (5s poll) spawns a replacement\n    }\n}(ctx, vols, mod)","preventionTips":["keep watched volume directories alive for the container's lifetime or accept degraded syncing","raise file-descriptor/watch limits on hosts running many containers with mounts","watch daemon logs for 'error running watcher' as an early signal of volume pruning races"],"tags":["inotify","watcher","logging","goroutine"],"backgroundTag":null,"analyzedSha":"c3a5f9184d83a197184f897a9f07eb3c01b3bc88","analyzedAt":"2026-08-15T18:58:08.334Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}