{"record":{"id":"09d1a658a04eea7b","repo":"AdguardTeam/AdGuardHome","slug":"watching-s-s-w","errorCode":null,"errorMessage":"watching %s %s: %w","messagePattern":"watching (.+?) (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/aghtls/defaultmanager.go","lineNumber":233,"sourceCode":"\n\terr := mgr.watcher.Remove(p)\n\tif err != nil {\n\t\terrs = append(errs, fmt.Errorf(\"unwatching %s %s: %w\", what, p, err))\n\t}\n\n\treturn errs\n}\n\n// appendWatchErr starts watching a file at path p described by what and\n// appends an error to the errs slice, if any.  Empty p is ignored.\nfunc (mgr *DefaultManager) appendWatchErr(errs []error, what, p string) (result []error) {\n\tif p == \"\" {\n\t\treturn errs\n\t}\n\n\terr := mgr.watcher.Add(p)\n\tif err != nil {\n\t\terrs = append(errs, fmt.Errorf(\"watching %s %s: %w\", what, p, err))\n\t}\n\n\treturn errs\n}\n\n// Refresh implements the [service.Refresher] interface for *DefaultManager.\nfunc (mgr *DefaultManager) Refresh(ctx context.Context) (err error) {\n\tmgr.logger.DebugContext(ctx, \"refreshing\")\n\n\tselect {\n\tcase mgr.updates <- UpdateSignal{}:\n\t\treturn nil\n\tcase <-ctx.Done():\n\t\treturn fmt.Errorf(\"refreshing: %w\", ctx.Err())\n\tdefault:\n\t\treturn nil\n\t}\n}","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/AdguardTeam/AdGuardHome/blob/b41aefbe51c8dde65e2c50f093996afa0502edf9/internal/aghtls/defaultmanager.go#L215-L251","documentation":"Failed to register a filesystem watch path with the fsnotify watcher inside the TLS manager. The wrapped error is the underlying watcher.Add failure (typically OS-level inotify limits or a path that no longer exists). This is collected into an aggregate error list when the manager sets up watches on certificate/key paths.","triggerScenarios":"Calling LoadTLSConfig/Refresh paths where the configured certificate or key path cannot be watched: path deleted or renamed after configuration, path is a non-existent directory, or the OS inotify watch limit (fs.inotify.max_user_watches) is exhausted.","commonSituations":"Cert-manager or acme.sh replaces certificate files atomically (rename/delete) between config load and watch registration; running in a container with a low inotify limit; misconfigured certificate path pointing to a missing file.","solutions":["Verify the configured certificate and private key paths exist on disk at startup","Raise the inotify watch limit: sysctl fs.inotify.max_user_watches=1048576","If files are replaced atomically, ensure the watched path is a stable directory path rather than the transient file","Inspect the wrapped error to distinguish ENOENT (missing path) from ENOSPC (watch limit)"],"exampleFix":"// before\ncert_path: /certs/live/example.com/fullchain.pem.new # renamed constantly\n// after\ncert_path: /certs/live/example.com/fullchain.pem","handlingStrategy":"validation","validationCode":"for _, p := range []string{certPath, keyPath} {\n    if _, err := os.Stat(p); err != nil {\n        return fmt.Errorf(\"path %s unusable: %w\", p, err)\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := mgr.LoadTLSConfig(ctx); err != nil {\n    // aggregate error; check strings.Contains for \"watching\" and the wrapped os error\n    log.Warn(\"cert watch setup failed; hot-reload disabled\", \"err\", err)\n}","preventionTips":["Pre-create certificate and key paths before manager initialization","Monitor inotify usage in containers and raise limits via sysctl","Watch stable directory paths when files are atomically replaced"],"tags":["tls","fsnotify","filesystem","watcher","inotify"],"backgroundTag":"inotify-watch-limit-exceeded","analyzedSha":"b41aefbe51c8dde65e2c50f093996afa0502edf9","analyzedAt":"2026-08-27T04:57:55.097Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}