{"record":{"id":"36ca1e1f9a162b23","repo":"AdguardTeam/AdGuardHome","slug":"creating-watcher-w","errorCode":null,"errorMessage":"creating watcher: %w","messagePattern":"creating watcher: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/aghos/fswatcher.go","lineNumber":82,"sourceCode":"\tevents chan Event\n\n\t// files maps directories to the files tracked in them.  If the tracked file\n\t// is a directory, it is mapped to itself.\n\tfiles map[string]*container.MapSet[string]\n}\n\n// osWatcherPref is a prefix for logging and wrapping errors in osWathcer's\n// methods.\nconst osWatcherPref = \"os_watcher\"\n\n// NewOSWatcher creates an [FSWatcher] that tracks the file system of the OS.  c\n// must not be nil.\nfunc NewOSWatcher(c *OSWatcherConfig) (w *OSWatcher, err error) {\n\tdefer func() { err = errors.Annotate(err, \"%s: %w\", osWatcherPref) }()\n\n\twatcher, err := fsnotify.NewWatcher()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"creating watcher: %w\", err)\n\t}\n\n\treturn &OSWatcher{\n\t\tlogger:  c.Logger,\n\t\tfilesMu: &sync.RWMutex{},\n\t\twatcher: watcher,\n\t\tevents:  make(chan Event, 1),\n\t\tfiles:   map[string]*container.MapSet[string]{},\n\t}, nil\n}\n\n// type check\nvar _ FSWatcher = (*OSWatcher)(nil)\n\n// Start implements the [service.Interface] interface for *OSWatcher.\nfunc (w *OSWatcher) Start(ctx context.Context) (err error) {\n\tgo w.handleErrors(ctx)\n\tgo w.handleEvents(ctx)","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/AdguardTeam/AdGuardHome/blob/b41aefbe51c8dde65e2c50f093996afa0502edf9/internal/aghos/fswatcher.go#L64-L100","documentation":"The companion rule to the start check: the end value of a day range must be minute-aligned. If end has seconds or sub-second components, validate returns 'end <val> isn't rounded to minutes' during schedule parsing.","triggerScenarios":"A schedule entry whose end includes seconds, e.g. \"17:30:45\" or a duration with a seconds component, in JSON or YAML weekly schedules.","commonSituations":"Computed end times derived from durations in seconds; third-party importers that preserve seconds; hand-edited configs with HH:MM:SS habits.","solutions":["Round end times down/up to whole minutes","Adjust the importer or script to strip seconds","Validate generated schedules locally before writing to config","Use the UI schedule editor"],"exampleFix":"// before\n\"end\": \"17:30:45\"\n// after\n\"end\": \"17:30\"","handlingStrategy":"validation","validationCode":"if end%time.Minute != 0 { end = end.Truncate(time.Minute) }","typeGuard":"func isMinuteAligned(d time.Duration) bool { return d%time.Minute == 0 }","tryCatchPattern":"if err := unmarshalSchedule(data); err != nil && strings.Contains(err.Error(), \"isn't rounded to minutes\") {\n    // round end and retry\n}","preventionTips":["Emit only HH:MM precision from generators","Round computed times to minutes at the source","Validate durations before serialization"],"tags":["schedule","validation","time","config","go"],"backgroundTag":"config-validation-failed","analyzedSha":"b41aefbe51c8dde65e2c50f093996afa0502edf9","analyzedAt":"2026-08-27T04:57:55.097Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}